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 triggerreceive()
function of contractTxAttackWallet
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:
receive()
- https://docs.soliditylang.org/en/v0.8.16/contracts.html#receive-ether-function .transfer()
and .call()
- https://docs.soliditylang.org/en/v0.8.16/types.html#members-of-addresses 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 writingmsg.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:
TxUserWallet(msg.sender)
- https://docs.soliditylang.org/en/v0.8.16/types.html#contract-types.balance
- https://docs.soliditylang.org/en/v0.8.16/units-and-global-variables.html#members-of-address-types
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.