0

It's been recommended that we should avoid using tx.origin.

However, there're some cases it's really needed, like the following case.


Assume we have two smart contracts: Sc1 and Sc2, both of which are created by the same owner. It first deployes Sc1 that maintains a token balance. Then, the owner creates another smart contract Sc2, deploys it and allows users to interact with Sc2, and transfer some of their tokens from Sc1 to Sc2 (so assume some users have tokens in Sc1). But only the token owners should be able to do that not anybody else (not even the contracts owner).

As you can see below I have to use tx.origin. I'm aware that if the contracts owner creates another invalid contract and make the user (token owners) call it then it will be able to decrement their token_bsalance in Sc1. Below is the code for the above case (the attack is excluded).

   contract Sc1{
      mapping (address => uint) token_balance;
      mapping (address => bool) valid_caller_contracts;// keeps track 
                                                      //of those contract that can call this contract
      address owner;
      function Sc1(){
        owner = msg.sender;
      }
       // only owner must be able to register. 
      function register_valid_contract() external {
          require(tx.origin==owner);
          valid_caller_contracts[msg.sender] = true;
      }
      function decrement_token(uint val) external{
        require(token_balance[tx.origin] >= val);
        token_balance[tx.origin] -=val;
      }
   }

   contract Sc2{
     mapping (address => uint) token_balance2;
     address owner;
     function SC2(){
        owner = msg.sender;
      }
      function register() external{
       Sc1 c1=Sc1(0x22);//Let's assum the address of Sc1 after it's 
                        //deployed is 0x22
       c1.register_valid_contract();
      }
      // only those who have tokens in Sc1 must be able to tranfer 
      //tokens to Sc2.
      function fetch_tokens(uint val) external{
       Sc1 c1=Sc1(0x22);//Let's assum the address of Sc1 after it's 
                        //deployed is 0x22
      c1.decrement_token(val);
      token_balance2[msg.sender] += val;
      }
    }

Question: In the above case, can we avoid using tx.origin?

2

A serious problem for a contract depending on tx.origin is that your contract will not work correctly with multisig wallets (or any other smart contract).

A solution for the problem with register_valid_contract is to pass the contract address as parameter and the owner has to call the function as an extra step.

function register_valid_contract(address sc2) external {
    require(msg.sender == owner);
    valid_caller_contracts[sc2] = true;
}

Another approach is that SC1 uses a factory patters to create SC2 and it will register them immediately at creation.


For decrement_token you can pass msg.sender as parameter, but you have to make sure the sender is a valid contract.

function decrement_token(address from, uint val) external{
    require(valid_caller_contracts[msg.sender]);
    require(token_balance[from] >= val);
    token_balance[from] -=val;
}

In this example if you define the trust relationship between SC1 y SC2, for example if SC1 creates SC2 then SC1 can check the sender address is SC2 and trust the parameters will be correct.

  • Thank you very much for the answer. Just wanted to mention that in your proposed solution (even though it's correct) the parties have to interact directly with SC1, while I was wondering if the can do that via SC2. Thanks again! – Ay. Mar 20 '18 at 10:30
  • The issue there is that you need to call SC1 from SC2, and SC1 doesn't know anything about SC2 (it can be an attacker). One solution is that the owner send a signature with the message, SC1 can recover the addres with ECRECOVER and validate it is from the owner. Another solution is to have a third contract create both SC1 and SC2 and register them mutually on creation. – Ismael Mar 20 '18 at 14:45
  • thanks for your comment. Regarding the first solution (if you meant the sender sends a message and its signature to SC2, then both SC1, and SC2 checks the signature), don't you think there will be a replay attack? Because the sender message and signature can be copied and send it from another contract to SC1.In this case, SC1 always validates it. – Ay. Mar 21 '18 at 10:43
  • You can include more info in the signature, the address of SC2, a timestamp, so SC1 can verify SC2 is the caller and the date of sign is close to the transaction. – Ismael Mar 21 '18 at 15:39

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.