简体   繁体   中英

`msg.sender` preferred over `tx.origin` in Solidity

I am new to solidity but I have read at many times that tx.origin should be avoided & msg.sender should be used. There is a given demo on this in solidity page . It says like:-

Never use tx.origin for authorization. Let's say you have a wallet contract like this:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
    address owner;

    constructor() {
        owner = msg.sender;
    }

    function transferTo(address payable dest, uint amount) public {
        // THE BUG IS RIGHT HERE, you must use msg.sender instead of tx.origin
        require(tx.origin == owner);
        dest.transfer(amount);    // .transfer is a global variable
    }
}

Now someone tricks you into sending Ether to the address of this attack wallet:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
interface TxUserWallet {
    function transferTo(address payable dest, uint amount) external;
}

contract TxAttackWallet {
    address payable owner;

    constructor() {
        owner = payable(msg.sender);
    }

    receive() external payable {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);   //  **LINE 1**
    }
}

Now, I want to know that how line 1 will drain entire funds from TxUserWallet . I think transfer() is a global variable which will just transfer the amount to address in dest which is of attack wallet. How .transfer() will trigger receive() function of contract TxAttackWallet . Secondly, In line TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); , why are we writing it as TxUserWallet(msg.sender) , like why are we adding (msg.sender) after contract name and also what values is being passed by writing msg.sender.balance ?

you should avoid using tx.origin to prevent the Reentrancy attack. solidity re-entrancy attack explanation

Basically, the attacker is withdrawing money from the contract recursively till the balance is 0. So there is a chain of function calls. A reentrancy attack involves two smart contracts. A vulnerable contract and an untrusted attacker's contract. ( fallback function is called under multiple conditions and this makes writing code for each condition hard. That is why receive is added. receive is called only contract receives ether so that fallback should be executed only if a function signature is not implemented within a contract.)

在此处输入图像描述

msg.sender is the last caller while tx.origin is the original caller of those recursive function chain.

funcA => funcB => funcC

msg.sender is the caller of funcC whereas tx.origin is the caller of funcA

Now to prevent the Reentrancy attack we have to make sure that there is no chain of function calls.

require(tx.origin == msg.sender)

In your question, story is a little different. Imagine the workflow. You are saying

Now someone tricks you into sending Ether to the address of this attack wallet:

You are sending money to TxAttackWallet , so you are initiating the function chains. tx.origin is you, the owner, tx.origin == owner . Once you send money to TxAttackWallet , automatically receive of TxAttackWallet will be called so this code will be executed recursively. You are in a loop

  TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);

You keep transferring funds from your contract to TxUserWallet .

Who is the tx.origin ? It is you because you started this chain of functions so this will always pass:

 require(tx.origin == owner); 

If you were using the msg.sender==owner , msg.sender would be the TxUserWallet so require condition would not pass and no funds would be transferred.

How .transfer() will trigger receive() function of contract TxAttackWallet

The receive() function is invoked automatically when you're sending ETH to the contract address without specifying the data field.

It is not possible to specify the data field with the .transfer() function, you'd need to use the low-level .call() for that.

Docs:


why are we writing it as TxUserWallet(msg.sender) , like why are we adding (msg.sender) after contract name and also what values is being passed by writing msg.sender.balance ?

TxUserWallet(msg.sender) is a contract type pointer to the msg.sender address (in your case the TxUserWallet contract address), expecting it to implement all public and external functions of TxUserWallet .

This way, the TxAttackWallet contract expects to be able to call the transferTo() function on msg.sender and expects to receive empty response, as the transferTo() function doesn't return any params.

msg.sender.balance returns the native balance (ETH on Ethereum, BNB on Binance Smart Chain, ...) of msg.sender , in your case that's the TxUserWallet contract address.

Docs:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM