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.