Understanding Transaction Context: tx.origin vs msg.sender
📖 Scenario: Imagine you are building a simple smart contract system where one contract calls another. You want to understand who is actually initiating the transaction and who is calling the contract functions.This is important because in blockchain, tx.origin and msg.sender can be different, and knowing the difference helps you write safer contracts.
🎯 Goal: You will create two contracts: Caller and Callee. The Callee contract will have a function that returns both tx.origin and msg.sender. The Caller contract will call the Callee contract's function. You will see the difference in the values returned.
📋 What You'll Learn
Create a contract named Callee with a function getOriginAndSender that returns tx.origin and msg.sender.
Create a contract named Caller with a function callCallee that calls getOriginAndSender from Callee.
Deploy both contracts and call callCallee from an externally owned account (your wallet).
Print the addresses returned by getOriginAndSender to observe the difference.
💡 Why This Matters
🌍 Real World
Understanding transaction context is crucial for writing secure smart contracts that interact with other contracts and users.
💼 Career
Blockchain developers must know how to correctly identify who is calling their contracts to prevent security vulnerabilities.
Progress0 / 4 steps
1
Create the Callee contract with getOriginAndSender function
Create a contract named Callee with a public function getOriginAndSender that returns two addresses: tx.origin and msg.sender.
Blockchain / Solidity
Hint
Remember to declare the function as public view and return two addresses.
2
Create the Caller contract with callCallee function
Create a contract named Caller with a function callCallee that takes an address calleeAddress and calls getOriginAndSender on the Callee contract at that address. Store the returned addresses in variables origin and sender.
Blockchain / Solidity
Hint
Use the Callee contract type to call getOriginAndSender on the given address.
3
Call callCallee from an external account
Write a comment explaining that when you call callCallee from your wallet, tx.origin will be your wallet address and msg.sender will be the Caller contract address.
Blockchain / Solidity
Hint
Explain the difference between tx.origin and msg.sender in a comment.
4
Print the returned addresses to observe the difference
Add a public function printOriginAndSender in the Caller contract that takes an address calleeAddress, calls callCallee, and returns a string showing both tx.origin and msg.sender addresses in a readable format using abi.encodePacked and string conversion.
Blockchain / Solidity
Hint
Use a helper function to convert addresses to strings for readable output.
Practice
(1/5)
1. What does tx.origin represent in a blockchain transaction?
easy
A. The original external account that started the transaction
B. The immediate caller of the current function
C. The contract address executing the function
D. The miner who included the transaction in a block
Solution
Step 1: Understand the meaning of tx.origin
tx.origin always points to the external account that initiated the transaction, regardless of how many contracts are called.
Step 2: Differentiate from msg.sender
msg.sender is the immediate caller, which can be a contract or an account, but tx.origin is always the original external user.
Final Answer:
The original external account that started the transaction -> Option A
Quick Check:
tx.origin = original external account [OK]
Hint: Remember: tx.origin is the original user, msg.sender is immediate caller [OK]
Common Mistakes:
Confusing tx.origin with msg.sender
Thinking tx.origin changes in internal calls
Assuming tx.origin is always a contract
2. Which of the following is the correct way to check if the caller is the owner using msg.sender in Solidity?
easy
A. require(owner == block.coinbase);
B. require(tx.origin == owner);
C. require(owner == tx.origin);
D. require(msg.sender == owner);
Solution
Step 1: Identify the correct variable for immediate caller
To check the caller of the function, msg.sender is used because it represents the immediate caller.
Step 2: Verify the syntax of the require statement
The correct syntax is require(msg.sender == owner); to ensure only the owner can proceed.
Final Answer:
require(msg.sender == owner); -> Option D
Quick Check:
Use msg.sender for permission checks [OK]
Hint: Use msg.sender for permission checks, not tx.origin [OK]
Common Mistakes:
Using tx.origin for permission checks
Swapping owner and caller in comparison
Using block.coinbase which is miner address
3. Consider this Solidity code snippet:
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?
medium
A. false
B. true
C. Compilation error
D. Revert due to permission error
Solution
Step 1: Understand owner assignment in constructor
The owner is set to msg.sender when the contract is deployed, which is the external user deploying it.
Step 2: Analyze checkOrigin() call by external user
When the external user calls checkOrigin(), tx.origin is the same external user who deployed the contract (owner), so the comparison returns true.
Final Answer:
true -> Option B
Quick Check:
Direct call: tx.origin equals owner [OK]
Hint: Direct calls: tx.origin equals msg.sender equals owner [OK]
Common Mistakes:
Assuming tx.origin differs from msg.sender on direct calls
What is the main issue with the callCheck function in Caller?
medium
A. It causes a compilation error due to missing payable keyword
B. It compares tx.origin instead of msg.sender inside checkOrigin
C. It uses low-level call incorrectly and ignores return value unpacking
D. It sets owner incorrectly in Callee constructor
Solution
Step 1: Analyze the use of low-level call in callCheck
The call returns (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 from checkOrigin(), leading to incorrect or misleading results.
Final Answer:
It uses low-level call incorrectly and ignores return value unpacking -> Option C
Quick Check:
Low-level call needs return data decoding [OK]
Hint: Low-level call returns bytes; decode to get function result [OK]
Common Mistakes:
Blaming tx.origin usage instead of call misuse
Ignoring that call returns (bool, bytes)
Assuming owner is set wrongly
5. You want to design a contract that only allows the original external user who started a transaction to execute a sensitive function, even if called through multiple contracts. Which is the safest way to implement this check?
hard
A. Use require(tx.origin == owner); to check the original external account
B. Use require(msg.sender == owner); to check the immediate caller
C. Use require(tx.origin == msg.sender); to ensure both are equal
D. Use require(owner == address(this)); to check contract address
Solution
Step 1: Understand the difference between msg.sender and tx.origin
msg.sender is the immediate caller, which can be a contract, while tx.origin is 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, use require(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 A
Quick Check:
Original user check uses tx.origin [OK]
Hint: Use tx.origin to track original external user through calls [OK]
Common Mistakes:
Using msg.sender which can be a contract
Comparing tx.origin to msg.sender incorrectly
Checking contract address instead of user
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.