How to limit token receiver callers to accepted token address? - solidity

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.

Related

What to do to solve unpredictable gas fees from a function in a smart contract using boolean?

The smart contract I have written seems to have an error where the gas fees are unpredictable and even if I increase the max limit, the transaction fails and gets reverted. This specific function uses a bool and am wondering if it is the cause.
I was trying to pass the function goodsDelivered with the exchangeID which is the transaction hash from executing function openExchange, however when I try changing the goodsDelivered to 'true', I get a warning from metamask stating: unpredictable_gas_limit and gets reverted. Initially I thought it was an error from the client side where I wrote js code to be able to interact with the smart contract but when I tried writing on etherscan directly there was also the transaction failed. I have also tried debugging on Remix but it functions fine on remix.
pragma solidity ^0.8.0;
contract Exchange{
struct exchange{
uint amount;
address buyer;
address seller;
address agent;
bool goodsDelivered;
}
constructor() {
}
uint nonce;
mapping (bytes32 => exchange) public exchangeRegistry;
function openExchange(address _seller, address _agent) external payable returns (bytes32 _exchangeId) {
address _buyer = msg.sender;
uint _amount = msg.value;
_exchangeId = keccak256(abi.encodePacked(_buyer, nonce));
exchangeRegistry[_exchangeId].amount = _amount;
exchangeRegistry[_exchangeId].buyer = _buyer;
exchangeRegistry[_exchangeId].seller = _seller;
exchangeRegistry[_exchangeId].agent = _agent;
nonce += 1;
}
function withdrawAmount(bytes32 _exchangeId) external payable returns (bool){
require((exchangeRegistry[_exchangeId].goodsDelivered == true && msg.sender == exchangeRegistry[_exchangeId].seller)
|| (exchangeRegistry[_exchangeId].goodsDelivered == false && msg.sender == exchangeRegistry[_exchangeId].buyer));
payable(msg.sender).transfer(exchangeRegistry[_exchangeId].amount);
return (true);
}
function goodsDelivered(bytes32 _exchangeId, bool _goodsDelivered) external returns (bool){
require(msg.sender == exchangeRegistry[_exchangeId].agent);
exchangeRegistry[_exchangeId].goodsDelivered = _goodsDelivered;
}
}

how to make onRecivedERC20 function?

I would like to make a function for receiving ERC20 in contract and after receiving ERC20 token it should transfer that ERC20 to another wallet.
the flow should be if a user uses that function first it should send that ERC20 to the contract and after that contract should forward that token to another wallet. I don't know where to start from
example transaction is this:
https://polygonscan.com/tx/0x88d85e4b746b65708a38b8f4c5d5bc0f73ff78e28868084eed565976b46df10e
The ERC-20 standard doesn't define how to notify a receiver contract about the incoming transfer. So you'll need to use either another standard (e.g. ERC-777) or build a custom notification hook.
Here's an example of such custom notification. It builds on top of the OpenZeppelin ERC-20 implementation, checks if the receiver is a contract - and if it is a contract, tries to call its onERC20Receive() function.
You can test it by deploying two separate contracts - MyToken and SomeReceiver - and then sending tokens from the deployer address to SomeReceiver. You can see that the ReceivedTokens event was emitted, as a result of invoking the function onERC20Receive when SomeReceiver received the tokens.
pragma solidity ^0.8.16;
import "#openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MyT") {
_mint(msg.sender, 1000 * 1e18);
}
function _afterTokenTransfer(address from, address to, uint256 amount) internal override {
if (to.code.length > 0) {
// token recipient is a contract, notify them
try IERC20Receiver(to).onERC20Receive(from, amount) returns (bool success) {
// the recipient returned a bool, TODO validate if they returned true
} catch {
// the notification failed (maybe they don't implement the `IERC20Receiver` interface?)
}
}
}
}
interface IERC20Receiver {
function onERC20Receive(address from, uint256 amount) external returns (bool);
}
contract SomeReceiver is IERC20Receiver {
event ReceivedTokens(address from, uint256 amount);
function onERC20Receive(address from, uint256 amount) external returns (bool) {
emit ReceivedTokens(from, amount);
return true;
}
}

Solidity - why does fallback() get called even though address.call{value:msg.value}("") does not have data?

The following contract calls another contract using an interface method (code to change):
pragma solidity 0.8.7;
interface MyStorage {
function setStorageValue(uint256) external;
}
contract StorageFactory {
uint256 storageValue;
constructor(uint256 _storageValue) {
storage = _storageValue;
}
function initStorage(MyStorage store) public payable {
store.setStorageValue(storageValue);
address payable storeAddress = payable(address(store));
storeAddress.call{value: msg.value}("");
}
}
Following is the StorageContract (code cannot be changed):
pragma solidity 0.8.7;
contract Storage {
int _storageValue;
function setStorageValue(int storageValue) public {
_storageValue = storageValue;
}
receive() external payable {
require(_storageValue == -1 || address(this).balance <= uint(_storageValue), "Invalid storage value");
}
fallback() external {
_storageValue = -1;
}
}
I use a test to call initStorage of the first contract by passing a Storage object, where the test is meant to fail because the value is set to a large amount. But somehow, the fallback() function seems to get called, setting the value to -1. I can't figure out why. Any help is appreciated.
Due to the solidity doc:
The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable.
Your function getting called because there's no overloading for the function
function setStorageValue(uint256 storageValue) public
So change the storageValue from int to uint256 will help.

Testing a Payable Function in Solidity

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

How to make an API call in solidity?

I have a smart contract that I’m trying to make, it pays out the winners of my League of Legends tournament. However I’m running into an issue. I need to make an API call to get the winner of the match, I have a simple URL that I’ve make.
"example-winner.com/winner"
And it returns simple JSON with the address of the winner:
{"winner":"0xa7D0......."}
However, I’m not sure how to make the API call to the outside function. I know I need to use some sort of oracle technology.
Any thoughts? Below is my code:
pragma solidity ^0.4.24;
contract LeagueWinners{
address public manager;
address[] public players;
uint256 MINIMUM = 1000000000000000;
constructor() public{
manager = msg.sender;
}
function enter() public payable{
assert(msg.value > MINIMUM);
players.push(msg.sender);
}
function getWinner() public{
assert(msg.sender == manager);
// TODO
// Get the winner from the API call
result = 0; // the result of the API call
players[result].transfer(address(this).balance);
// returns an adress object
// all units of transfer are in wei
players = new address[](0);
// this empties the dynamic array
}
}
You can use Chainlink as your Oracle.
As many have mentioned, you will need an oracle to get your API call. Something that is important to note, your contract is actually asking an oracle to make your API call for you, and not making the API call itself. This is because the blockchain is deterministic. For more information see this thread.
To answer your question, you can use the decentralized oracle service Chainlink.
You'd add a function:
function getWinner()
public
onlyOwner
{
Chainlink.Request memory req = buildChainlinkRequest(JOB, address(this), this.fulfill.selector);
req.add("get", "example-winner.com/winner");
req.add("path", "winner");
sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
}
For the purpose of the following exmaple, we are going to pretend you want to return a uint256 instead of an address. You can return a bytes32 and then convert it to an address, but for simplicity let's say the API returns the index of the winner. You'll have to find a node and jobId that can make a http.get request and return a uint256 object. You can find nodes and jobs from market.link. Each testnet (Ropsten, Mainnet, Kovan, etc) has different node addresses, so make sure you pick the right ones.
For this demo, we are going to use LinkPool's ropsten node
address ORACLE=0x83F00b902cbf06E316C95F51cbEeD9D2572a349a;
bytes32 JOB= "c179a8180e034cf5a341488406c32827";
Ideally, you'd choose a number of nodes to run your job, to make it trustless and decentralized. You can read here for more information on precoordinators and aggregating data. disclosure I am the author of that blog
Your full contract would look like:
pragma solidity ^0.6.0;
import "github.com/smartcontractkit/chainlink/evm-contracts/src/v0.6/ChainlinkClient.sol";
contract GetData is ChainlinkClient {
uint256 indexOfWinner;
address public manager;
address payable[] public players;
uint256 MINIMUM = 1000000000000000;
// The address of an oracle
address ORACLE=0x83F00b902cbf06E316C95F51cbEeD9D2572a349a;
//bytes32 JOB= "93fedd3377a54d8dac6b4ceadd78ac34";
bytes32 JOB= "c179a8180e034cf5a341488406c32827";
uint256 ORACLE_PAYMENT = 1 * LINK;
constructor() public {
setPublicChainlinkToken();
manager = msg.sender;
}
function getWinnerAddress()
public
onlyOwner
{
Chainlink.Request memory req = buildChainlinkRequest(JOB, address(this), this.fulfill.selector);
req.add("get", "example-winner.com/winner");
req.add("path", "winner");
sendChainlinkRequestTo(ORACLE, req, ORACLE_PAYMENT);
}
// When the URL finishes, the response is routed to this function
function fulfill(bytes32 _requestId, uint256 _index)
public
recordChainlinkFulfillment(_requestId)
{
indexOfWinner = _index;
assert(msg.sender == manager);
players[indexOfWinner].transfer(address(this).balance);
players = new address payable[](0);
}
function enter() public payable{
assert(msg.value > MINIMUM);
players.push(msg.sender);
}
modifier onlyOwner() {
require(msg.sender == manager);
_;
}
// Allows the owner to withdraw their LINK on this contract
function withdrawLink() external onlyOwner() {
LinkTokenInterface _link = LinkTokenInterface(chainlinkTokenAddress());
require(_link.transfer(msg.sender, _link.balanceOf(address(this))), "Unable to transfer");
}
}
This would do about everything you need.
If you can't adjust the API to return a uint, you can return a bytes32 and then convert it to an address or a string.
function bytes32ToStr(bytes32 _bytes32) public pure returns (string memory) {
bytes memory bytesArray = new bytes(32);
for (uint256 i; i < 32; i++) {
bytesArray[i] = _bytes32[i];
}
return string(bytesArray);
}
You cannot. The vm does not have any I/O outside of the blockchain itself. Instead you will need to tell your smart contract who the winner is and then the smart contract can just read the value of that variable.
This design pattern is also known as the "oracle". Google "Ethereum oracle" for more info.
Basically your web server can call your smart contract. Your smart contract cannot call your web server. If you need your smart contract to access a 3rd party service then your web server will need to make the request then forward the result to solidity by calling a function in your smart contract.
You didn't properly explain what you are trying to do. Are you having trouble with the solidity code? or rather with your server? Here is an edited version. See if it helps.
pragma solidity ^0.4.24;
contract LeagueWinners{
address public manager;
//address[] public players;
uint256 MINIMUM = 1000000000000000;
constructor() public{
manager = msg.sender;
}
struct Player {
address playerAddress;
uint score;
}
Player[] public players;
// i prefer passing arguments this way
function enter(uint value) public payable{
assert(msg.value > MINIMUM);
players.push(Player(msg.sender, value));
}
//call this to get the address of winner
function winningPlayer() public view
returns (address winner)
{
uint winningScore = 0;
for (uint p = 0; p < players.length; p++) {
if (players[p].score > winningScore) {
winningScore = players[p].score;
winner = players[p].playerAddress;
}
}
}
// call this to transfer fund
function getWinner() public{
require(msg.sender == manager, "Only a manager is allowed to perform this operation");
// TODO
address winner = winningPlayer();
// Get the winner from the API call
//uint result = 0; // the result of the API call
winner.transfer(address(this).balance);
// returns an adress object
// all units of transfer are in wei
delete players;
// this empties the dynamic array
}
}
At least that is what I understand by your question.