'ValidationError on smart contract function call for no apparent reason(web3py)?
I am trying to call Uniswap's Router's function swapExactTokensForETHSupportingFeeOnTransferTokens(). When I enter the values manually on etherscan, it goes through. However, when I do it via python code it gives me a validation error. The error looks like this:
web3.exceptions.ValidationError: Could not identify the intended function with name swapExactTokensForETHSupportingFeeOnTransferTokens, positional argument(s) of type (<class int>, <class int>, <class list>, <class str>, <class float>) and keyword argument(s) of type {}. Found 1 function(s) with the name swapExactTokensForETHSupportingFeeOnTransferTokens: [swapExactTokensForETHSupportingFeeOnTransferTokens(uint256,uint256,address[],address,uint256)] Function invocation failed due to no matching argument types.
Here's the code im using:
swap = uniswap_router_contract.functions.swapExactTokensForETHSupportingFeeOnTransferTokens(uint amount, 0, list_of_two_token_addresses, my_address_string, unix_time_stamp_deadline).buildTransaction({'nonce': some_nonce})
gas_amount = web3.eth.estimateGas(swap)
print(gas amount)
Am I supposed to somehow turn my ints into unsigned int in python? I tried but it didn't fix it. Im using the web3py library. Could someone direct me to the issue or to existing code that calls said function?
Thanks.
Edit:
I converted timestamp into int and also made sure my address strings were checksum using the web3.toChecksum method.
swap = uniswap_router_contract.functions.swapExactTokensForETHSupportingFeeOnTransferTokens(uint amount, 0, list_of_two_token_addresses, my_address_string, int(unix_time_stamp_deadline)).buildTransaction({'nonce': some_nonce})
gas = web3.eth.estimateGas(swap)
print(gas)
When I run this it gives me this error:
raise SolidityError(response['error']['message']) web3.exceptions.SolidityError: execution reverted: TransferHelper: TRANSFER_FROM_FAILED
Solution 1:[1]
The arguments types that you are passing do not match the expected argument types for the function.
You are passing:
int, int, list, str, float
but the function expects:
uint256, uint256, address[], address, uint256
I'm guessing that it is the last argument, unix_time_stamp_deadline
, that is causing the mismatch. It is a float, but the function expects an int. You can convert it to an int as you pass it to the function like this:
int(unix_time_stamp_deadline)
Solution 2:[2]
I reported the problem in the following article: https://medium.com/@italo.honorato/how-to-resolve-transferhelper-error-transfer-from-failed-fb4c8bf6488c
In it I put the solution of the case.
I went through this problem. The error only happens when selling tokens through a smart swap contract using the above function. If you interact directly through the Uniswap V2 router contract on Etherscan you will have no problem as msg.sender is your external account.
For some reason the error doesn't seem to happen with other functions like swapExactTokensForTokens
in fee-free contracts.
Remember to first provide approvals for the exchange agreement to use WETH tokens and your token 2. This must be done before executing exchanges.
The exchange function code is as follows:
contract ContractSwapOnUniswap{
function swap(address _tokenIn, address _tokenOut, uint256 _amountIn, uint256 _amountOutMin, address _to) external {
IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn);
//next we need to allow the uniswapv2 router to spend the token we just sent to this contract
//by calling IERC20 approve you allow the uniswap contract to spend the tokens in this contract
IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn);
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(_amountIn, _amountOutMin, path, _to, block.timestamp);
}
}
Read the comments for the approve
function of the contract above.
Below is the approve
function of the ERC20 standard:
function approve(address spender, uint256 amount) public override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
The error happens because it is not possible to approve a permission for the UNISWAP_V2_ROUTER
to spend tokens from the ContractSwapOnUniswap
address, why by default the ERC20 approve function will not recognize ContractSwapOnUniswap
as msg.sender
. That is, the TransferHelper: TRANSFER_FROM_FAILED
error occurs because the UNISWAP_V2_ROUTER
does not have the necessary permission in the token contract to spend balance of ContractSwapOnUniswap
.
It is not possible for ContractSwapOnUniswap
to be msg.sender
. The msg.sender
is always the account that calls the contract function and sends the transaction.
I concluded that this is a design error of the ERC20 standard itself applied to contracts that swap other accounts. If I'm wrong, please correct me.
That is, swapExactTokensForTokensSupportingFeeOnTransferTokens
apparently doesn't work very well if used in this way for both buying and selling.
The problem here is mixing transferFrom
with approve in the same function logic.
The solution I found for this is the following code:
function buy(address _tokenIn, address _tokenOut, uint256 _amountIn, uint256 _amountOutMin) public {
IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn);
IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn);
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(_amountIn, _amountOutMin, path, address(this), block.timestamp);
}
function sell(address _tokenIn, address _tokenOut, uint256 _amountIn, uint256 _amountOutMin) public {
//No `transferFrom` statement needed here
//Sell only works if this line below exists
//approve below is giving permission for UNISWAP_V2_ROUTER to spend msg.sender tokens
//But who has the tokens to be spent is addres(this)
//Looks like the EVM still needs `msg.sender` to be approved
//Precisely because the transaction involves the manipulation of the token, even if it is not spent by `msg.sender`
IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn);
address[] memory path = new address[](2);
path[0] = _tokenIn;
path[1] = _tokenOut;
IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(_amountIn, _amountOutMin, path, msg.sender, block.timestamp);
}
Remember that it is recommended to insert a function to withdraw ERC20 or ETH from your contract, as without this there is a risk of losing them forever within the contract.
strong text
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | mhawke |
Solution 2 |