Discover how a simple difference in who calls a function can protect your blockchain contract from hackers!
Transaction context (tx.origin vs msg.sender) in Blockchain / Solidity - When to Use Which
Imagine you are manually tracking who started a payment and who is calling a function in a blockchain contract by writing notes on paper every time a transaction happens.
You try to remember if the payment came directly from the user or through another contract, but it quickly becomes confusing and messy.
Manually tracking transaction origins is slow and error-prone because you can easily mix up the original sender and the immediate caller.
This confusion can cause security problems, like letting unauthorized users perform actions they shouldn't.
The concepts of tx.origin and msg.sender in blockchain smart contracts clearly tell you who started the transaction and who called the current function.
This helps you write safer and clearer code without guessing or manual tracking.
if (caller == originalUser) { proceed(); } // but who is caller or originalUser?
if (msg.sender == tx.origin) { proceed(); } // clear roles defined by blockchainIt enables smart contracts to securely distinguish between the original transaction sender and intermediate callers, preventing unauthorized access.
For example, a contract can check if a user is calling directly or through another contract to avoid phishing attacks that trick contracts into doing harmful actions.
Manually tracking transaction origins is confusing and risky.
tx.origin and msg.sender provide clear, automatic context.
Using them helps write safer blockchain contracts.
Practice
tx.origin represent in a blockchain transaction?Solution
Step 1: Understand the meaning of
tx.origintx.originalways points to the external account that initiated the transaction, regardless of how many contracts are called.Step 2: Differentiate from
msg.sendermsg.senderis the immediate caller, which can be a contract or an account, buttx.originis always the original external user.Final Answer:
The original external account that started the transaction -> Option AQuick Check:
tx.origin= original external account [OK]
- Confusing tx.origin with msg.sender
- Thinking tx.origin changes in internal calls
- Assuming tx.origin is always a contract
msg.sender in Solidity?Solution
Step 1: Identify the correct variable for immediate caller
To check the caller of the function,msg.senderis used because it represents the immediate caller.Step 2: Verify the syntax of the require statement
The correct syntax isrequire(msg.sender == owner);to ensure only the owner can proceed.Final Answer:
require(msg.sender == owner); -> Option DQuick Check:
Use msg.sender for permission checks [OK]
- Using tx.origin for permission checks
- Swapping owner and caller in comparison
- Using block.coinbase which is miner address
contract Test {
address owner;
constructor() {
owner = msg.sender;
}
function checkOrigin() public view returns (bool) {
return tx.origin == owner;
}
}If an external user calls
checkOrigin() directly, what will be the returned value?Solution
Step 1: Understand owner assignment in constructor
The owner is set tomsg.senderwhen the contract is deployed, which is the external user deploying it.Step 2: Analyze
When the external user callscheckOrigin()call by external usercheckOrigin(),tx.originis the same external user who deployed the contract (owner), so the comparison returns true.Final Answer:
true -> Option BQuick Check:
Direct call: tx.origin equals owner [OK]
- Assuming tx.origin differs from msg.sender on direct calls
- Confusing owner initialization
- Expecting errors from this code
contract Caller {
address owner;
function callCheck(address callee) public view returns (bool) {
(bool success, ) = callee.call(abi.encodeWithSignature("checkOrigin()"));
return success;
}
}
contract Callee {
address owner;
constructor() {
owner = msg.sender;
}
function checkOrigin() public view returns (bool) {
return tx.origin == owner;
}
}What is the main issue with the
callCheck function in Caller?Solution
Step 1: Analyze the use of low-level call in callCheck
Thecallreturns (bool, bytes), but the code only returns the bool from call without decoding the actual function's return value.Step 2: Understand the consequence of ignoring return data
Because the function ignores the returned bytes, it cannot get the actual boolean result fromcheckOrigin(), leading to incorrect or misleading results.Final Answer:
It uses low-level call incorrectly and ignores return value unpacking -> Option CQuick Check:
Low-level call needs return data decoding [OK]
- Blaming tx.origin usage instead of call misuse
- Ignoring that call returns (bool, bytes)
- Assuming owner is set wrongly
Solution
Step 1: Understand the difference between msg.sender and tx.origin
msg.senderis the immediate caller, which can be a contract, whiletx.originis the original external account that started the transaction.Step 2: Choose the check that ensures original user authorization
To ensure only the original external user can execute the function, userequire(tx.origin == owner);because it tracks the transaction origin through multiple calls.Final Answer:
Use require(tx.origin == owner); to check the original external account -> Option AQuick Check:
Original user check uses tx.origin [OK]
- Using msg.sender which can be a contract
- Comparing tx.origin to msg.sender incorrectly
- Checking contract address instead of user
Ready to prove it?
Pass the test and this pattern moves unknown
