Who called the caller?

0xAE 0x00

CALLSTACKDEPTH

How many calls deep the current code is running.

0xAE 0x01

CALLERN

The caller of any frame further up the call stack.

MIP-18 is in Review and is not active on any network. It adds two opcodes under MIP-7's 0xAE prefix. The spec says they are for future Monad protocol upgrades and does not name them yet.

The information on this page should not be quoted. Please refer to MIP-18 for the authoritative spec.

Step through a call stack

Click a frame to run code there, then change n. CALLERN(n) returns what CALLER would return n frames up.

alice sends a transaction to an aggregator. It calls a router, the router calls a pool, and the pool calls a token.

Call frames

alice · sends the transaction

Click a frame to make it the one running.

CALLSTACKDEPTH

3

CALLERN(n)

n=1

Router

CALLER in F2

Calls to precompiles also add a frame and count toward depth. No code runs in that frame to read it.

The two opcodes

Both use the first two selectors after 0xAE. Neither takes argument bytes, so the encoding rules for extension arguments don't apply. How 0xAE works (MIP-7)

BytesNamePopsPushesGas
0xAE 0x00CALLSTACKDEPTHnothingdepth2
0xAE 0x01CALLERNncaller2
AE00

CALLSTACKDEPTH

Pushes the current depth. The transaction's own frame is depth 0.

6001AE01

PUSH1 1 CALLERN

Pushes the caller of the frame that called you. n comes from the stack, so it can be computed at runtime.

AE00AE01

CALLSTACKDEPTH CALLERN

Uses the depth as n. The result is always the same as ORIGIN.

2 gas covers the whole two-byte opcode, the same as CALLER and ADDRESS. The client already holds the call stack in memory, so both run in constant time.

Don't use CALLERN for access control

A treasury lets only the admin withdraw. Compare checking CALLER with checking CALLERN(1).

The attacker gets the admin to call ClaimAirdrop. That contract calls Treasury.withdraw() and sends the funds to the attacker.

admin→ClaimAirdrop→Treasury.withdraw()

require(CALLER == admin)

CALLER = ClaimAirdrop

reverts

CALLER is ClaimAirdrop. The check fails and the funds stay put.

require(CALLERN(1) == admin)

CALLERN(1) = admin

passes

The admin is one frame up, so the check passes. The attacker's contract withdraws the funds.

This is the same problem as checking tx.origin. The spec says contracts should not use CALLERN(n) with n above 0 for authorization.

Who this affects

Contract developers

CALLER and ORIGIN, plus every frame between

CALLERN(0) is CALLER and CALLERN(depth) is ORIGIN. The callers in between could not be read from inside a contract before.

Auditors

Flag CALLERN in auth checks

A check on CALLERN(1) or higher can be passed by any contract the privileged account is tricked into calling.

Simulators, AA bundlers, analyzers

Behavior can depend on the path

A contract can act differently depending on how deep it is or who sits higher in the stack. Tools that assume only the direct caller, calldata and state matter may need changes.

The spec gives no use case beyond future Monad upgrades in development at Category Labs. Until those are published, the page can show what the opcodes return, but not which feature needs them.

Continue the discussion on Monad Forum

Questions, feedback, or a better idea? Weigh in on the forum thread.

Open forum thread