Understanding truffle test - solidity

I am following a tutorial to create a smart contract using truffle. I would like to have a better understanding about the test created in the tutorial.
This is one of the tests:
it("increases myDonationsCount", async () => {
const currentDonationsCount = await fundraiser.myDonationsCount(
{from: donor}
);
await fundraiser.donate({from: donor, value});
const newDonationsCount = await fundraiser.myDonationsCount(
{from: donor}
);
assert.equal(
1,
newDonationsCount - currentDonationsCount,
"myDonationsCount should increment by 1");
})
where does this object come from? {from: donor, value}
And for this test:
it("throws an error when called from a different account", async () =>{
try {
await fundraiser.setBeneficiary(newBeneficiary, {from: accounts[3]});
assert.fail("withdraw was not restricted to owners")
} catch(err) {
const expectedError = "Ownable: caller is not the owner"
const actualError = err.reason;
assert.equal(actualError, expectedError, "should not be permitted")
}
})
In the 3rd line from the above test they are passing 2 parameters fundraiser.setBeneficiary(newBeneficiary, {from: accounts[3]});.
How is this possible if the original function only receives one?
The function:
function setBeneficiary(address payable _beneficiary) public onlyOwner {
beneficiary = _beneficiary;
}

It's the transaction params.
Truffle allows you to override the default transaction params by specifying the overrides in the last argument, after you've passed all arguments defined in the Solidity function.
Docs: https://www.trufflesuite.com/docs/truffle/getting-started/interacting-with-your-contracts#making-a-transaction

Related

Test to withdraw funds only by owner in solidity

I need to write a test in solidity to test a withdrawFunds function. This function has an isOwner modifier which tests if the address of the sender is the same as the address stored as the owner's as part of the constructor. The problem is that when I try to invoke the withdrawFunds function, the function fails and says that I am not the owner. Is there any way I can test this functionality? Would proxy contracts help here? Any insight would be very helpful.
PS: I added an event to log the address stored as the owner's as part of the constructor (msg.sender) and to log the address the function receives as msg.sender. Both of these are different and I am not sure how to invoke this function as the owner.
In solidity you should define a function to return the contract owner:
// assuming you already set the owner in constructor
function getContractOwner() public view returns (address)
{
return owner;
}
Then in test file
// accounts is passed by truffle. this testing in truffle environment
contract("YourContract", (accounts) => {
let _contract = null;
let buyer = null;
// set the contract
before(async () => {
_contract = await YourContractName.deployed();
buyer = accounts[1];
});
describe("Normal withdraw", () => {
let currentOwner = null;
before(async () => {
currentOwner = await _contract.getContractOwner();
});
// now you have got the currentOwner
it("should fail when withdrawing by different than owner address", async () => {
const value = "10000000000000000";
_contract.withdraw(value, { from: buyer });
// add the logic for assertion
});
}
}

How to test the Solidity fallback() function via Hardhat?

I have a Solidity smart contract Demo which I am developing in Hardhat and testing on the RSK Testnet.
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract Demo {
event Error(string);
fallback() external {
emit Error("call of a non-existent function");
}
}
I want to make sure that the fallback function is called and the event Error is emitted. To this end, I am trying to call a nonExistentFunction on the smart contract:
const { expect } = require('chai');
const { ethers } = require('hardhat');
describe('Demo', () => {
let deployer;
let demoContract;
before(async () => {
[deployer] = await ethers.getSigners();
const factory = await ethers.getContractFactory('Demo');
demoContract = await factory.deploy().then((res) => res.deployed());
});
it('should invoke the fallback function', async () => {
const tx = demoContract.nonExistentFunction();
await expect(tx)
.to.emit(demoContract, 'Error')
.withArgs('call of a non-existent function');
});
});
However Hardhat throws a TypeError even before it actually connects to the smart contract on RSK:
Demo
1) should invoke the fallback function
0 passing (555ms)
1 failing
1) Demo
should invoke the fallback function:
TypeError: demoContract.nonExistentFunction is not a function
at Context.<anonymous> (test/Demo.js:13:29)
at processImmediate (internal/timers.js:461:21)
How can I outsmart Hardhat/Ethers.js and finally be able to call non-existent function thus invoking the fallback function in the smart contract?
For reference, this is my hardhat.config.js
require('#nomiclabs/hardhat-waffle');
const { mnemonic } = require('./.secret.json');
module.exports = {
solidity: '0.8.4',
networks: {
hardhat: {},
rsktestnet: {
chainId: 31,
url: 'https://public-node.testnet.rsk.co/',
accounts: {
mnemonic,
path: "m/44'/60'/0'/0",
},
},
},
mocha: {
timeout: 600000,
},
};
You can use the approach of injecting a non-existent function signature
into the smart contract object (ethers.Contract).
Create a function signature for nonExistentFunction:
const nonExistentFuncSignature =
'nonExistentFunction(uint256,uint256)';
Note that the parameter list should contain no whitespace,
and consists of parameter types only (no parameter names).
Then instantiate a new smart contract object.
When you do this you need to modify the ABI for Demo
such that it includes this additional function signature.:
const fakeDemoContract = new ethers.Contract(
demoContract.address,
[
...demoContract.interface.fragments,
`function ${nonExistentFuncSignature}`,
],
deployer,
);
Note that the contract that is deployed is the same as before -
it does not contain this new function.
However the client interacting with this smart contract -
the tests in this case -
does think that the smart contract has this function now.
At this point, you'll be able to run your original test,
with a minor modification:
const tx = fakeDemoContract[nonExistentFuncSignature](8, 9);
await expect(tx)
.to.emit(demoContract, 'Error')
.withArgs('call of a non-existent function');
Full test:
it('should invoke the fallback function', async () => {
const nonExistentFuncSignature = 'nonExistentFunc(uint256,uint256)';
const fakeDemoContract = new ethers.Contract(
demoContract.address,
[
...demoContract.interface.fragments,
`function ${nonExistentFuncSignature}`,
],
deployer,
);
const tx = fakeDemoContract[nonExistentFuncSignature](8, 9);
await expect(tx)
.to.emit(demoContract, 'Error')
.withArgs('call of a non-existent function');
});
Test result:
Demo
✔ should invoke the fallback function (77933ms)
1 passing (2m)
A transaction executing a function contains the function selector following its (ABI-encoded) input params in the data field.
The fallback() function gets executed when the transaction data field starts with a selector that does not match any existing function. For example an empty selector.
So you can generate a transaction to the contract address, with empty data field, which invokes the fallback() function.
it('should invoke the fallback function', async () => {
const tx = deployer.sendTransaction({
to: demoContract.address,
data: "0x",
});
await expect(tx)
.to.emit(demoContract, 'Error')
.withArgs('call of a non-existent function');
});
Note: If you also declared the receive() function, it takes precedence over fallback() in case of empty data field. However, fallback() still gets executed for every non-empty mismatching selector, while receive() is only invoked when the selector is empty.

How to receive a value returned by a Solidity smart contract transacting function?

I am writing an NFT smart contract which I am going to test via Hardhat and deploy on RSK.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "#openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract MyNFT is ERC721URIStorage {
uint private _counter;
address private _owner;
constructor() ERC721("My NFT", "MNFT") {
_owner = msg.sender;
}
function owner() public view returns (address) {
return _owner;
}
function mintNFT(address recipient, string memory tokenURI)
public returns (uint256)
{
require(msg.sender == owner(), "Only owner is allowed to mint");
uint newItemId = ++_counter;
ERC721._mint(recipient, newItemId);
ERC721URIStorage._setTokenURI(newItemId, tokenURI);
return newItemId;
}
}
Here I have two public functions: owner and mintNFT both returning some values. In my tests I would like to read the return values coming from these two functions. These are the tests I am running on Hardhat:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("My NFT", () => {
let deployer;
let myNFT;
// deploy NFT before the tests
before(async () => {
[deployer] = await ethers.getSigners();
const MyNFT = await ethers.getContractFactory('MyNFT');
myNFT = await MyNFT.deploy();
await myNFT.deployed();
});
describe('Receiving a value returned by a view function', () => {
it('The deployer should be the s/c owner', async () => {
const owner = await myNFT.owner();
expect(owner).to.equal(deployer.address);
});
});
describe('Receiving a value returned by a transacting function', () => {
it('Should return a correct ID of the newly minted item', async () => {
const newMintItem = {
id: 1,
uri: 'ipfs://Qme3QxqsJih5psasse4d2FFLFLwaKx7wHXW3Topk3Q8b14',
};
const newItemId = await myNFT.mintNFT(deployer.address, newMintItem.uri);
expect(newItemId).to.equal(newMintItem.id);
});
});
});
In the case of the owner function I get what I expect: It returns my account address, and the first test passes successfully. However, when it comes to the mintNFT function, I don't get what I expect: Instead of the newly created item ID I get something very different and my second test fails.
Why do two very similar tests give me different results? How do I get a return value from a function that sends a transaction?
For reference, this is the hardhat.config.js file I'm using:
require("#nomiclabs/hardhat-waffle");
module.exports = {
solidity: "0.8.4",
defaultNetwork: 'rskregtest',
networks: {
rskregtest: {
chainId: 33,
url: 'http://localhost:4444',
},
},
};
Values returned from a transaction are not available outside of EVM.
You can either emit an event, or create a public view getter function of the value.
contract MyNFT is ERC721URIStorage {
// `public` visibility autogenerates view function named `_counter()`
uint public _counter;
event NFTMinted(uint256 indexed _id);
function mintNFT(address recipient, string memory tokenURI)
public returns (uint256)
{
// ...
emit NFTMinted(newItemId);
return newItemId;
}
}
it('Should return a correct ID of the newly minted item', async () => {
const newMintItem = {
id: 1,
uri: 'ipfs://Qme3QxqsJih5psasse4d2FFLFLwaKx7wHXW3Topk3Q8b14',
};
// testing the emitted event
await expect(myNFT.mintNFT(deployer.address, newMintItem.uri))
.to.emit(myNFT, "NFTMinted")
.withArgs(newMintItem.id);
// testing the getter value
const counter = await myNFT._counter();
expect(counter).to.equal(newMintItem.id);
});
Docs: https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#emitting-events
You can not directly receive a return value coming from a function that you are sending a transaction to off-chain. You can only do so on-chain - i.e. when one SC function invokes another SC function.
However, in this particular case, you can obtain the value through events, as the ERC721 specification includes a Transfer event:
/// #dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
In Ethers.js, which you are already using in your hardhat tests, instead of smart contract function return value, you get a transaction response object returned on this line:
const txResponse = await myNFT.mintNFT(deployer.address, newMintItem.uri);
Next you need to get a transaction receipt by calling wait on txResponse:
const txReceipt = await txResponse.wait();
In turn, txReceipt contains an events array which includes all events emitted by your transaction. In this case, expect it to include a single Transfer event indicating the first minted item transfer from the zero address to your account address. Get this event by extracting the first element from the events array:
const [transferEvent] = txReceipt.events;
Next take tokenId property from the transferEvent arguments and this will be the Id of your newly minted NFT:
const { tokenId } = transferEvent.args;
The whole test should look as follows:
describe('Receiving a value returned by a transacting function', () => {
it('Should return a correct ID of the newly minted item', async () => {
const newMintItem = {
id: 1,
uri: 'ipfs://QmY6KX35Rg25rnaffmZzGUFb3raRhtPA5JEFeSSWQA4GHL',
};
const txResponse = await myNFT.mintNFT(deployer.address, newMintItem.uri);
const txReceipt = await txResponse.wait();
const [transferEvent] = txReceipt.events;
const { tokenId } = transferEvent.args;
expect(tokenId).to.equal(newMintItem.id);
});
});

Mock with Waffle return undefined

I have two contracts, one returns a static value (10)
I'd like to mock this value and return 2 (for instance), I'm trying with "mock" according to the documentation, but I still get 10
my test is:
describe('test', () => {
async function setup() {
const [sender, receiver] = new MockProvider().getWallets();
const mockGetEtherPrice = await deployMockContract(sender, GetEtherPrice.abi);
const contractFactory = new ContractFactory(NumbersMarketContract.abi, NumbersMarketContract.bytecode, sender);
console.log(mockGetEtherPrice);
const contract = await contractFactory.deploy();
return {sender, receiver, contract, mockGetEtherPrice};
}
it('returns false if the wallet has less then 1000000 coins', async () => {
const {contract, mockGetEtherPrice} = await setup();
await mockGetEtherPrice.mock.getPriceMocked.returns(2);
console.log(await contract.check())
expect(await contract.check()).to.be.equal(10);
});
});
My GetEtherPrice contract is:
contract GetEtherPrice {
function getPrice() public pure returns(uint256){
return getPriceMocked();
}
function getPriceMocked() public pure returns(uint256){
return 10;
}
}
In the main contract, I use 'is' to import my contract like:
contract NumbersMarketContract is GetEtherPrice
....
function check() public pure returns (uint256){
return getPrice();
}

Truffle and Ganache-cli test case fails

So I have this setup : truffle and ganache-cli
I'm sending some ether to my contract, here is the related part of my contract:
mapping(address => uint256) public balanceOf;
function () payable public {
uint amount = msg.value;
balanceOf[msg.sender] += amount;
}
In truffle this is how I send the ether.
it("Test if can be payed", function(){
return web3.eth.sendTransaction({
from:fromAddr,
to:MyContract.address,
value:amountToSend
}).then(function(res){
expect(res).to.not.be.an("error"); // test passed
});
});
it("Test if contract received ether", function(){
return web3.eth.getBalance(MyContract.address,
function(err, res){
expect(parseInt(res)).to.be.at.least(1000000000000000000); // test passed
});
});
it("Catch if balanceOf "+fromAddr, function(){
return sale.balanceOf.call(fromAddr).then(function(res){
expect(parseInt(res)).to.be.at.least(1); // fails the test
});
});
Am I doing it right? What could be the reason for failed test?
truffle test output :
AssertionError: expected 0 to be at least 1
+ expected - actual
-0
+1
I can provide more info if needed.
UPDATE :
for clarification on sale which is global variable.
it("Test if MyContract is deployed", function(){
return MyContract.deployed().then(function(instance){
sale = instance;
});
});
I think this is what you are looking for:
File path:
test/vault.js
const Vault = artifacts.require("Vault");
contract("Vault test", async accounts => {
// Rely on one instance for all tests.
let vault;
let fromAccount = accounts[0];
let oneEtherInWei = web3.utils.toWei('1', 'ether');
// Runs before all tests.
// https://mochajs.org/#hooks
before(async () => {
vault = await Vault.deployed();
});
// The `receipt` will return boolean.
// https://web3js.readthedocs.io/en/1.0/web3-eth.html#gettransactionreceipt
it("Test if 1 ether can be paid", async () => {
let receipt = await web3.eth.sendTransaction({
from: fromAccount,
to: vault.address,
value: oneEtherInWei
});
expect(receipt.status).to.equal(true);
});
it("Test if contract received 1 ether", async () => {
let balance = await web3.eth.getBalance(vault.address);
expect(balance).to.equal(oneEtherInWei);
});
// In Web3JS v1.0, `fromWei` will return string.
// In order to use `at.least`, string needs to be parsed to integer.
it("Test if balanceOf fromAccount is at least 1 ether in the contract", async () => {
let balanceOf = await vault.balanceOf.call(fromAccount);
let balanceOfInt = parseInt(web3.utils.fromWei(balanceOf, 'ether'));
expect(balanceOfInt).to.be.at.least(1);
});
});
You can see the full project here. Do note that I'm using Truffle v5 and Ganache v2. See the README file inside that GitLab repository.
Back to your question, there were 2 mistakes:
The sale is not defined. I have a feeling that you were actually referring to MyContract.
In order to use the least method in ChaiJS, you need to make sure you are passing integers. The balanceOf call is returning BigNumber or BN object and you can't use it with .least method.
FYI, Truffle v5 is now using BN by default (previously BigNumber). More about it here.
Let me know if this helps.