0

i have an erc20 token called TokenA, i want the user to be able to call a function from my smart contract that does some calculations and then transfers X amount of TokenA to the smart contract.

the problems is in the openzeppelin implementation if i call tokenA.transfer(address(this),amount) in the smart contract msg.sender would be the address of the smart contract but i want it to be the address of the user, and i know its impossible to change msg.sender so is there a workaround to this or should i change the implementation of openzeppelin and add another variable sender?

2 Answers 2

0

I would strongly advise against modifying the OpenZeppelin implementations.

You basically want to introduce a vulnerability where an account could spend funds belonging to another account without relying on explicit authorization.

Do not do that.

To make this work, you should rely on the allowance mechanism already included in OpenZeppelin ERC-20 implementation.

  1. Account A approves account B to spend funds on its behalf.
  2. Account B spends funds from account A to account C.

The spending in (2) is secured by the allowance mechanism present in the ERC-20 contract, and was explicitely allowed by the owner : Account A.

Alternatively, if you have control over the ERC-20 contract, you could simply inherit from it in your smart contract, no need for allowance then, as the user would be interacting directly with the contract holding the tokens, msg.sender would be the user's account address.


EDIT : Added code example to demonstrate the non preservation of msg.sender. Following the comments on the other answer.

pragma solidity ^0.8.0;

contract B {
    function testMsgSender(address supposedMsgSender) public view {
        require(supposedMsgSender == msg.sender, "msg.sender is not the right address");
    }
}

contract A {
    B contractInstance;
    
    constructor(address Baddress)
    {
        contractInstance = B(Baddress);
    }
    
    function test() public view {
        contractInstance.testMsgSender(msg.sender);
    }
}

You can deploy this code on Remix.

  1. Deploy B
  2. Deploy A by giving B's address to the contructor

Call A.test and you will see the following error message :

"msg.sender is not the right address"

2
  • that requires doing it from outside the smart contract like using web3, i guess this is the only solution because even if i could do it i wouldn't because as you said its not as transparent as asking the user to approve tokens.
    – ezio
    Oct 30, 2021 at 12:04
  • Yes, either your smart contract inherits from ERC-20 and you can do it all in one place, or you need the user to explicitely and directly express approval to the ERC-20 contract.
    – hroussille
    Oct 30, 2021 at 12:24
0

In order to transfer tokens to a smart contract, you will use transferFrom() function which takes 3 parameters: the sender(which is msg.sender-address of the user), the receiver(address(this)-address of smart contract), and the amount you want to send. Remember that transferFrom() function has to be called after the approve() function(because you first need to approve the amount of tokens you send to the contract). So it will be:

User => contractInstance.approve(address(this),amount);

In ContractA:

contractInstance.transferFrom(msg.sender, address(this), amount);

contractInstance being the instance of the token contract.

In order to test the approve() function with truffle, you explicitly need to approve the amount of tokens and then call the function from your contract where you have transferFrom(). Later on when you'll build your front end, you can initiate the request from there.

10
  • contractInstance.approve(address(this),amount); wouldn't work as inside contractInstance msg.sender would be the address of the calling contract, not the user address.
    – hroussille
    Oct 30, 2021 at 10:52
  • Yes but the OpenZeppelin doc specifies that approve "Sets amount as the allowance of spender over the caller’s tokens." Assuming initial user is User A, that the code where your exemple lives is contract B and contractInstance is contract C. Inside contract B msg.sender == User A , but inside contractInstance msg.sender == contract B, because the direct caller is contract B and not user A. So you wouldn't be approving contract B to spend funds on behalf of User A. not sure if that makes it clearer ?
    – hroussille
    Oct 30, 2021 at 11:32
  • When I'm refering to contractInstance I'm refering to the instance of the ERC20 token contract, not the contract used to transfer tokens to.I've edited my answer.
    – Icarus23
    Oct 30, 2021 at 11:33
  • Yes, contractInstance is the ERC-20 contract. But it won't work with : contractInstance.approve(address(this),amount); You say it yourself, " you explicitly need to approve the amount of tokens" and this is true, you need msg.sender be the user's address for the approval to work, it can only be so if the user is directly calling on the ERC-20 contract, any intermediary will make that call non explicit, and the msg.sender value will change. Your code approves address(this) to spend funds on behalf of address(this). Not on behalf of the user's address.
    – hroussille
    Oct 30, 2021 at 11:44
  • True, but you're using the instance of the token contract to call it's transfer method inside ContractA, So it will go User=> Contract A, not user=>contractA=>contractB. You declare ERC20 contractInstance and pass the address of the contractInstance in the constructor.Yes, you're correct, the call must be made by the user and he has to approve the tokens sent to ContractA,and woudn't that happen if he calls the function approve() from ContractA where ContractA is address(this)?
    – Icarus23
    Oct 30, 2021 at 11:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.