How to pass data to eth_sendTransaction? - solidity

I'm calling the function "confirmVote(NUMBER)" from my contract.
In this contract I do not have the "fallcack" or "receive" functions, as the contract does not receive a token (ETH), users only pay a gas fee.
However, it is giving an error in the transaction, most likely it is because the data is empty.
From what I understand, I must pass my function called as data, how do I do this?
const CONTRACT_SIGNER = PROVIDER_CONTRACT.web3.connect(useEthers.states.provider.web3.getSigner());
const ESTIMAGE_GAS = await CONTRACT_SIGNER.estimateGas.confirmVote(candidateID);
await window.ethereum.request({
method: 'eth_sendTransaction',
params: [
{
data: ????,
from: '',
to: '',
gas: '',
value: 0,
},
],
});
const TRANSACTION_RECEIPT = await WEB3_PROVIDER.waitForTransaction(TRANSACTION as string);
if (TRANSACTION_RECEIPT.status === 1) {
await CONTRACT_SIGNER.confirmVote(candidateID)
}

If CONTRACT_SIGNER is an ethers contract instance you can get the encoded data for a function call as follows
const data = CONTRACT_SIGNER.interface.encodeFunctionData('confirmVote', [5])
Usually you dont have to do this as it is abstracted away in the ethers contract instance - to perform the confirmVote(uint) transaction you only need the line
await CONTRACT_SIGNER.confirmVote(candidateID)

Related

How to call Openbrush contract from Front-end app

I implement smart contracts with ink!, substrate's WASM smart contract implementation language.
At that time, I decided to use the openbrush library. openbrush is like openzeppelin in EVM.
The smart contract was easy to implement according to the official website. [https://docs.openbrush.io/]
But I couldn't figure out how to call it from the front end.
I was able to solve it by looking at Telegram, but I will write this in the hope that it will help others.
A function defined in openbrush is declared like this:
psp34::transfer (to: TransferInput1, id: TransferInput2, data: TransferInput3)
psp34::ownerOf (id: OwnerOfInput1): Option<AccountId>
Below is a sample code that calls the above two contract functions.
const wsProvider = new WsProvider(blockchainUrl);
const api = await ApiPromise.create({ provider: wsProvider });
const contract = new ContractPromise(api, psp_abi, PSP_ADDRESS);
setApi(api);
const { gasConsumed, result, output } = await contract.query['psp34::ownerOf'](
actingAddress,
{ value: 0, gasLimit: -1 },
NFT_ID
);
const { web3FromSource } = await import("#polkadot/extension-dapp");
const wsProvider = new WsProvider(blockchainUrl);
const api = await ApiPromise.create({ provider: wsProvider });
setApi(api);
const contract = new ContractPromise(api, psp_abi, PSP_ADDRESS);
const performingAccount = accounts[0];
const injector = await web3FromSource(performingAccount.meta.source);
const flip = await contract.tx['psp34::transfer'](
{ value: 0, gasLimit: gasLimit },
"ajYMsCKsEAhEvHpeA4XqsfiA9v1CdzZPrCfS6pEfeGHW9j8",
NFT_ID,
Null
);
if (injector !== undefined) {
const unsub = await flip.signAndSend(
actingAddress,
{ signer: injector.signer },
({ events = [], status }) => {
-- snip --

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);
});
});

Understanding truffle test

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

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.