I have a basic ERC-20 Token defined as
import "#openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Token is ERC20 {
constructor() ERC20("Token", "TOKEN") {}
function mint(address account, uint256 amount) public {
_mint(account, amount);
}
}
I have another contract Item that has a method I want to cost 1 Token, basing answer off https://ethereum.stackexchange.com/a/78911/30804
import "#openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Item {
IERC20 private token;
constructor(address tokenAddress) {
token = IERC20(tokenAddress);
}
function myMethod() public {
bool success = token.transferFrom(msg.sender, address(this), 1 ether); // also tried just 1
require(success, "Insufficient Funds: Requires 1 Token");
// do things
}
}
I'm running this inside a Hardhat test
const TokenFactory = await ethers.getContractFactory("Token");
Token = await Token.deploy();
await Token.mint(owner.address, ethers.utils.parseUnits("1", 18));
await Token.approve(owner.address, ethers.utils.parseUnits("1", 18));
const ItemFactory = await ethers.getContractFactory("Item");
Item = await ItemFactory.deploy(Token.address);
await Item.myMethod(); // this is the line that errors
Everything runs and I can see debug code from the solidity contracts, but I keep getting an error that
reverted with reason string 'ERC20: insufficient allowance'
that I traced back to the openzeppelin internal function _spendAllowance
After some debugging I thought that the culprit was because I needed to approve, so I added a call to approve but I'm still getting the same error
I'm assuming my issue is something basic can anyone see what it might be? Thanks in advance.
await Token.approve(owner.address, ethers.utils.parseUnits("1", 18));
You're giving the approval to the owner address, but the actual msg.sender inside the transferFrom() function is the Item address (as the caller of the function is the Item contract).
Solution: Approve the Item address as the spender.
// after the `Item` contract has been deployed
await Token.approve(Item.address, ethers.utils.parseUnits("1", 18));
await Item.myMethod();
Related
I want to create a payable token
which includes a function transferAndCall(TokenReceiver to, uint256 amount, bytes4 selector).
By calling this function, you can transfer tokens to the TokenReceiver smart contract address,
and then call onTransferReceived(address from,uint tokensPaid, bytes4 selector) on the receiver,
which in turn invokes a function specified in thebytes4 selector on the receiver.
Note that this is similar to/ inspired by ERC1363.
Here is a simplified version of my receivable token:
import "#openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MeowToken is ERC20 {
constructor() ERC20("MeowToken", "MEO") {
ERC20._mint(msg.sender, 10_000_000);
}
function transferAndCall(
TokenReceiver to,
uint256 amount,
bytes4 selector
) external {
ERC20.transfer(address(to), amount);
to.onTransferReceived(msg.sender, amount, selector);
}
}
And this is a token receiver:
contract TokenReceiver {
address acceptedToken;
event PurchaseMade(address from, uint tokensPaid);
modifier acceptedTokenOnly () {
require(msg.sender == address(acceptedToken), "Should be called only via the accepted token");
_;
}
constructor(address _acceptedToken) {
acceptedToken = _acceptedToken;
}
function onTransferReceived(
address from,
uint tokensPaid,
bytes4 selector
) public acceptedTokenOnly {
(bool success,) = address(this).call(abi.encodeWithSelector(selector, from, tokensPaid));
require(success, "Function call failed");
}
function purchase(address from, uint tokensPaid) public acceptedTokenOnly {
emit PurchaseMade(from, tokensPaid);
}
}
I want to make sure that public functions on the receiver are only called via the payable token.
For this reason I added acceptedTokenOnly modifier to both of them.
However after adding the modifier my test began to fail:
it('Transfer Tokens and call Purchase', async () => {
const tokenAmount = 100;
const tx = meowToken.transferAndCall(
tokenReceiver.address,
tokenAmount,
tokenReceiver.interface.getSighash('purchase'),
);
await expect(tx)
.to.emit(tokenReceiver, 'PurchaseMade')
.withArgs(deployer.address, tokenAmount);
});
1) Transfer and call
Transfer Tokens and call Purchase:
Error: VM Exception while processing transaction: reverted with reason string 'Function call failed'
Why does this happen?
How to make sure the receiver's functions are invoked only by the accepted token?
For reference, I am developing and testing smart contracts in Hardhat and deploying on RSK.
When you're doing this:
(bool success,) = address(this).call(abi.encodeWithSelector(selector, from, tokensPaid));
you're making an external call, meaning that msg.sender will become address(this).
Now the modifier acceptedTokenOnly during function purchase will fail since msg.sender isn't the token anymore.
Suggested changing the function to this:
function purchase(address from, uint tokensPaid) public {
require(msg.sender == address(this), "wrong sender");
emit PurchaseMade(from, tokensPaid);
}
The problem is, you are using low level call method, here:
(bool success,) = address(this).call(abi.encodeWithSelector(selector, from, tokensPaid));
This changes the value of msg.sender inside onTransferReceived from the accepted token to the receiver itself.
Here is one way to achieve what you want:
Replace call with delegatecall.
This will solve your problem instantly.
Unlike call, the delegatecall will invoke your function on behalf of the caller smart contract:
function onTransferReceived(
address from,
uint tokensPaid,
bytes4 selector
) public acceptedTokenOnly {
(bool success,) = address(this).delegatecall(abi.encodeWithSelector(selector, from, tokensPaid));
require(success, "Function call failed");
}
Apart from switching from call to delegatecall, as mentioned in #Juan's answer, there is a more "manual" approach:
Do not use call altogether, and instead invoke the functions by name.
This can be accomplished using an if ... else control structure that compares the selector parameter with the intended function selector (purchase):
function onTransferReceived(
address from,
uint tokensPaid,
bytes4 selector
) public acceptedTokenOnly {
if (selector == this.purchase.selector) {
purchase(from, tokensPaid);
} else {
revert("Call of an unknown function");
}
}
While this is more tedious to do, it might be preferable from a security point of view.
For example, if you wish to white-list the functions that you allow to be called through
this mechanism.
Note that the approach using call/ delegatecall exposes a potential vulnerability
for arbitrary (and possibly unintended) function execution.
I have a basic Solidity smart contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestContract {
uint256 public _blockTimestamp;
function accumulated() public payable returns (uint256) {
_blockTimestamp = block.timestamp;
return 1;
}
}
And using remix IDE I can compile it and check the value of _blockTimestamp changing after executing the accumulated function.
Now, I can deploy the contract and check the value of the public variable without any problem with etherjs:
const provider = new providers.JsonRpcProvider(getEnv('RINKEBY_NODE_URL'));
const wallet = new Wallet(getEnv('ROPSTEN_PRIVATE_KEY'), provider);
const TestContract = TestContract__factory.connect(getEnv('TEST_CONTRACT'), provider);
const _blockTimestamp = await NFTLTokenContract._blockTimestamp();
The problem is when I try to execute the function accumulated. Being a payable function I need a signer:
const provider = new providers.JsonRpcProvider(getEnv('RINKEBY_NODE_URL'));
const wallet = new Wallet(getEnv('ROPSTEN_PRIVATE_KEY'), provider);
const TestContract = TestContract__factory.connect(getEnv('TEST_CONTRACT'), provider);
const _blockTimestamp = await TestContract._blockTimestamp();
const accumulated = await TestContract.accumulated('1', wallet);
But I still get the error:
Error: sending a transaction requires a signer
(operation="sendTransaction", code=UNSUPPORTED_OPERATION,
version=contracts/5.5.0)
What am I missing?
You are trying to call a non-view function by using only a provider, you must provide a signer in order to call the function.
Try connecting to the contract using the wallet as the signer:
const TestContract = TestContract__factory.connect(getEnv('TEST_CONTRACT'), wallet);
So I'm trying to test a payable function on the following smart contract here using the truffle framework:
contract FundMe {
using SafeMathChainlink for uint256;
mapping(address => uint256) public addressToAmountFunded;
address[] public funders;
address public owner;
AggregatorV3Interface public priceFeed;
constructor(address _priceFeed) public {
priceFeed = AggregatorV3Interface(_priceFeed);
owner = msg.sender;
}
function fund() public payable {
uint256 mimimumUSD = 50 * 10**18;
require(
getConversionRate(msg.value) >= mimimumUSD,
"You need to spend more ETH!"
);
addressToAmountFunded[msg.sender] += msg.value;
funders.push(msg.sender);
}
I specifically want to test the payable function, and I've seen a few things on the internet where people create other contracts with initial balances and then send their testing contract some eth. But I would just like to grab a local ganache wallet and send some eth to the contract and then test that, if someone could show me some test javascript code to wrap my head around this that would be much appreciated!
For a contract to be able to receive ETH (or any native token - BNB on Binance Smart Chain, TRX on Tron network, ...) without invoking any function, you need to define at least one of these functions receive() (docs) or fallback() (docs).
contract FundMe {
// intentionally missing the `function` keyword
receive() external payable {
// can be empty
}
// ... rest of your code
}
Then you can send a regular transaction to the contract address in truffle (docs):
const instance = await MyContract.at(contractAddress);
await instance.send(web3.toWei(1, "ether"));
Note that because receive() and fallback() are not regular functions, you cannot invoke them using the truffle autogenerated methods: myContract.functionName()
If you want to execute a payable function sending it ETH, you can use the transaction params (docs). It's always the last argument, after all of the regular function arguments.
const instance = await MyContract.at(contractAddress);
await instance.fund({
value: web3.toWei(1, "ether")
});
Note: If the fund() function had 1 argument (let's say a bool), the transaction params would be the 2nd:
await instance.fund(true, {
value: web3.toWei(1, "ether")
});
I have this contract deployed that is meant to send ether from itself to an another account
pragma solidity ^0.8.0;
contract Contract {
address public owner;
address public admin;
constructor(address _admin) public {
owner = msg.sender;
admin = _admin;
}
modifier onlyOwner(address sender) {
require(
sender == admin,
"Only the admin of the contract can perform this operation."
);_;
}
function sendTo (
address toAddress
) public payable onlyOwner(msg.sender) {
payable(toAddress).transfer(msg.value);
}
}
and I try to interact with it like so on the client side:
var contract = new web3.eth.Contract(abi, Address, null);
const transaction = {
from: mainAcct,
to: dummyAcct,
value: '10000000000000000',
gas: 30000
};
await contract.methods.sendTo(dummyAcct).send(
transaction , function(err, hash){
if(!err){
console.log("Transaction hash:", hash);
}else{
console.log(err);
}
});
} catch (e) {
console.log(e);
}
why do I get this error in the console:
Error: Transaction has been reverted by the EVM
Looks like you need to fund the contract before sending the amount out
I did something like below hre is hardhat object, but there will be way to add funds while deploying
const waveContractFactory = await hre.ethers.getContractFactory('contract_name');
const waveContract = await waveContractFactory.deploy({
value: hre.ethers.utils.parseEther('0.1'),
});
The problem could be with transfer, transfer limit the gas and that could be the reason of the error, try using call instead of transfer, check this web to see how to use it https://solidity-by-example.org/sending-ether/
The code example is https://github.com/facuspagnuolo/ethereum-spiking/tree/master/5-token-sale-contract
The related files are:
1. contracts\MyToken.sol
contract MyToken is BasicToken, Ownable {
uint256 public constant INITIAL_SUPPLY = 10000;
function MyToken() {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
Transfer(0x0, msg.sender, INITIAL_SUPPLY);
}
}
2. contracts\TokenSale.sol
contract TokenSale is Ownable {
MyToken public token;
uint256 public priceInWei;
bool public tokenSaleClosed;
event TokenPurchase(address buyer, address seller, uint256 price, uint256 amount);
function TokenSale(MyToken _token, uint256 _price) public {
if (_price < 0) return;
token = _token;
priceInWei = _price;
tokenSaleClosed = false;
}
}
3. migrations\2_deploy_contracts.js
const MyToken = artifacts.require("./MyToken.sol");
const TokenSale = artifacts.require("./TokenSale.sol");
module.exports = function(deployer) {
deployer.deploy(MyToken);
deployer.deploy(TokenSale);
};
when I deploy it by truffle and testrpc ($ truffle migrate), it fails as the following:
Using network 'development'.
Running migration: 2_deploy_contracts.js
Deploying MyToken...
... 0x289577d078c8fbc61585127ac123dbef43aa711529bf079c4fd400206c65e0de
MyToken: 0x33ddda65330e75e45d3d2e4e270457915883c2fc
Deploying TokenSale...
Error encountered, bailing. Network state unknown. Review successful transactions manually.
Error: TokenSale contract constructor expected 2 arguments, received 0
at C:\Users\zklin\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\truffle-contract\contract.js:390:1
at new Promise ()
at C:\Users\zklin\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\truffle-contract\contract.js:374:1
at
at process._tickCallback (internal/process/next_tick.js:188:7)
http://truffleframework.com/docs/getting_started/migrations#deployer
// Deploy A, then deploy B, passing in A's newly deployed address
deployer.deploy(A).then(function() {
return deployer.deploy(B, A.address);
});
Might work migrating only the second one so TokenSale and automatically it will be deployed MyToken.