Skip to main content
mip.watch
⌘K

MIP-18

Call Stack Introspection Opcodes

Review Standards Track Core GitHub ↗ Forum ↗
Idea Draft Review Last Call Final Living

Add extension opcodes to inspect the depth and caller chain of the current call stack

Authors
Category Labs (@category-labs)
Created
2026-09-24
Updated
Nov 4, 2026

Stakeholder Impact

What this MIP changes for each audience. Severity and action flags are model-derived with deterministic floors per MIP type/category.

TL;DR

This MIP introduces two new opcodes, `CALLSTACKDEPTH` and `CALLERN`, to enhance the Ethereum Virtual Machine's ability to inspect the call stack. These additions aim to improve user identification in complex transaction scenarios and bolster reentrancy protection mechanisms.

What's Changing

  • New Opcode: `CALLSTACKDEPTH` provides the current depth of the call stack, allowing contracts to understand their position in the call hierarchy.
  • New Opcode: `CALLERN` allows contracts to access the caller's address at any specified depth in the call stack, enhancing user identification.
  • Improved User Identification: Applications can directly determine the original user behind routers or aggregators without relying on unverified claims.
  • Enhanced Reentrancy Protection: Consumers can check if a producer is an ancestor in the call stack, preventing potential exploits during external calls.
  • Constant-Time Implementation: Both opcodes are designed to execute in constant time, ensuring efficiency in their usage.
⌘

Developers

Smart contract & dapp builders, RPC consumers, tooling authors

The introduction of the `CALLSTACKDEPTH` and `CALLERN` opcodes will require developers to adapt their contracts if they wish to utilize these new functionalities, as existing contracts that attempt to use these opcodes will previously halt with an INVALID error. Additionally, tooling and SDKs may need updates to support these new opcodes in their interfaces.

Medium
◉

Users

Wallet users, EOA holders, dapp visitors

This change introduces new opcodes that allow smart contracts to inspect the call stack, which may enhance security and functionality in dapps. However, as an end user, you will not notice any immediate changes to transaction fees, confirmation times, or wallet compatibility.

Low
◆

Validators

Node operators, RPC operators, delegators

The introduction of the CALLSTACKDEPTH and CALLERN opcodes does not require a mandatory client upgrade, but it may lead to increased usage of gas due to the additional opcode costs. However, the economic impact on validator revenue is likely neutral as these changes do not directly affect block rewards or fee distributions.

Medium
✦

Foundation

Monad Foundation & Category Labs core devs

This MIP introduces new opcodes that require governance coordination for activation and may necessitate updates to documentation and communication with ecosystem partners regarding their implementation and use cases.

High action required

Generated Nov 12, 2026, 9:48 AM · model: gpt-4o-mini

Specification

Cite

## Abstract

This MIP allocates two extension opcodes under the `EXTENSION` (`0xAE`) mechanism defined in [MIP-7](./MIP-7.md). `CALLSTACKDEPTH` pushes the depth of the current call frame in the EVM call stack. `CALLERN` generalizes `CALLER`: it pops an index `n` and pushes the caller address `n` frames above the current frame. Both opcodes take no immediate arguments and have constant-time implementations.

## Motivation

### Identifying Users Behind Routers

Applications such as AMMs, referral systems or trading venues with tiered fee models may wish to determine the user on whose behalf a router or transaction aggregator is acting. The `ORIGIN` of a transaction identifies the end user only when that user is an EOA that submitted the transaction directly; it cannot be used when the user is a smart account, or when the transaction is submitted by a relayer, bundler or sponsor on the user's behalf. As a result, current implementations must rely on the entire call stack forwarding the transaction's user through calldata. This requires every intermediate contract to cooperate, and the forwarded value is an unverified claim made by the router.

With `CALLERN`, an application can instead read the user directly: if its immediate caller (`CALLERN(0)`) is a known router, the router's own caller (`CALLERN(1)`) is the user, and this extends hop by hop through a contiguous chain of known routers and aggregators.

### Reentrancy Protection

In *read-only reentrancy*, a contract (the producer) makes an external call while its state is temporarily inconsistent, and during that window a third-party contract (the consumer) reads derived values such as a price or exchange rate from it. Several 2023 exploits of lending protocols that used liquidity-pool prices as oracles followed this pattern.

Existing mitigations, including locks held in transient storage ([EIP-1153](https://eips.ethereum.org/EIPS/eip-1153)), require the producer to set a lock and expose it. Producers that are already deployed and cannot be upgraded cannot add such a lock, which leaves their consumers without a defence.

`CALLERN` lets a consumer enforce this protection without the producer's help: it iterates `n` from `0` to `CALLSTACKDEPTH` and reverts if `CALLERN(n)` equals the producer's address. Any external call made by the producer creates a frame whose caller is the producer, so every frame created by that call has the producer among its ancestors' callers and the check cannot miss it. Because callers can add frames but cannot remove ancestors, an attacker cannot evade the check. This is the sound, "revert if contract X is an ancestor" form of check described in [Security Considerations](#security-considerations). As a consequence, the consumer also reverts when reached through a legitimate callback from the producer, while the producer's state may be inconsistent.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.html) and [RFC 8174](https://www.ietf.org/rfc/rfc8174.html).

### Parameters

| Name                      | Value  |
| ------------------------- | ------ |
| `CALLSTACKDEPTH_SELECTOR` | `0x00` |
| `CALLERN_SELECTOR`        | `0x01` |
| `G_CALLSTACKDEPTH`        | 2      |
| `G_CALLERN`               | 2      |

### Extension Selectors

| Extended Opcode | Mnemonic         | Immediates | Stack Input | Stack Output |
| --------------- | ---------------- | ---------- | ----------- | ------------ |
| `0xAE 0x00`     | `CALLSTACKDEPTH` | None       | —           | `depth`      |
| `0xAE 0x01`     | `CALLERN`        | None       | `n`         | `caller`     |

Neither extended opcode is followed by argument bytes. The byte following the selector is decoded as the next instruction.

### Call Frames

A **call frame** is created for each message executed by the EVM: the top-level message of a transaction, and each message created by `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`, `CREATE` and `CREATE2`. Calls to precompiled contracts create a frame for the purpose of depth accounting, but no code in that frame can observe it.

Let the frames active during execution be `F_0, F_1, ..., F_d`, where `F_0` is the frame of the transaction's top-level message and `F_d` is the currently executing frame. `d` is the **depth** of the current frame.

Let `sender(F_i)` be the value that `CALLER` returns when executed in frame `F_i`. In particular, `sender(F_0)` is the transaction origin, and for a frame created by `DELEGATECALL` or `CALLCODE`, `sender(F_i)` follows the existing semantics of `CALLER` in those frames.

### Opcode Semantics

#### `CALLSTACKDEPTH`

`CALLSTACKDEPTH` pushes `d` onto the stack.

#### `CALLERN`

`CALLERN` pops a 256-bit unsigned integer `n` from the stack, then:

- If `n <= d`, it pushes `sender(F_{d - n})` onto the stack, zero-extended to 32 bytes.
- Otherwise, it pushes `0` onto the stack.

As a result:

- `CALLERN(0)` is equivalent to `CALLER`.
- `CALLERN(CALLSTACKDEPTH)` is equivalent to `ORIGIN`.

### Gas Costs

`CALLSTACKDEPTH` costs `G_CALLSTACKDEPTH` gas. `CALLERN` costs `G_CALLERN` gas, regardless of the value of `n`. These costs apply to the complete extended opcode; no additional gas is charged for the `EXTENSION` prefix.

### Exceptional Conditions

Both opcodes are subject to the standard exceptional halting conditions for stack underflow, stack overflow and insufficient gas. `CALLERN` MUST NOT halt exceptionally because `n` is out of range.

## Rationale

### Frame Indexing

`CALLERN` is indexed so that `CALLERN(0)` coincides with `CALLER`, which makes the new opcode a strict generalization of the existing one. `CALLSTACKDEPTH` is 0-based, so that `CALLERN(CALLSTACKDEPTH)` coincides with `ORIGIN`, and no index is wasted.

### Out-of-Range Indices

An index above the current depth returns `0` rather than halting, following the precedent of `BLOCKHASH` for out-of-range lookups. This lets contracts probe the call stack without first reading its depth.

### Delegated Frames

`CALLERN` reports the same value that `CALLER` would report in each ancestor frame. As a result, `DELEGATECALL` and `CALLCODE` frames can cause the same address to appear at consecutive indices. This definition is the simplest one to specify and implement, and it ensures that every value returned by `CALLERN` was also observable by `CALLER` in some ancestor frame.

### Gas Costs

Both opcodes read state that an implementation already holds in memory for the duration of the transaction, and they can be implemented in constant time. Their cost therefore matches `CALLER` and `ADDRESS`.

### Encoding

Neither opcode needs immediate arguments, so neither uses the argument encodings defined in MIP-7. `CALLERN` takes its index from the stack instead of an immediate so that it can be computed at runtime (for example, from `CALLSTACKDEPTH`).

## Backwards Compatibility

Under MIP-7, the extended opcodes `0xAE 0x00` and `0xAE 0x01` currently behave like `INVALID`. Existing code that executes either sequence halts exceptionally today and will execute successfully after this MIP. Such code is not expected to exist in practice, because the sequences have never had defined behavior on Monad or Ethereum.

A trailing `EXTENSION` byte has no selector and must halt exceptionally, as [MIP-7](./MIP-7.md) specifies.

`JUMPDEST` analysis is unaffected.

## Security Considerations

### Authorization

`CALLERN` makes the identity of every ancestor caller available to a contract, not just its immediate caller. Using `CALLERN` for authorization inherits the weaknesses of authorizing with `ORIGIN`: an attacker who persuades a privileged account to call an attacker-controlled contract can relay calls that appear to originate from that account. Contracts should not use `CALLERN(n)` for `n > 0` for authorization.

Contracts that nonetheless inspect ancestors should note that arbitrary-depth ancestor checks are *asymmetric*; callers can insert frames but cannot remove them. This means that checks of the form "revert if contract X is an ancestor" are sound (the worst case being a false-positive revert), but checks of the form "allow if contract X is an ancestor" are *unsound* (the worst case being a false-positive trust assumption).

### Context-Dependent Behavior

Both opcodes let a contract behave differently depending on how it was reached. Tools that assume a contract's behavior depends only on its immediate caller, calldata and state, such as transaction simulation, account abstraction validation and static analysis, may need to account for this.

### Call Depth

`CALLSTACKDEPTH` exposes information that was previously only observable indirectly, by approaching the call depth limit. The 63/64 gas forwarding rule already makes the depth limit unreachable in practice, so this is not expected to create new griefing vectors.

## References

- [MIP-7: Extension Opcodes](./MIP-7.md)

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

Forum discussion

2 posts · 2 likes · last month
Read more on forum ↗
  1. @Bruce Collie #1 Sep 29, 2026, 1:56 PM

    Monad Improvement Proposals Monad Improvement Proposals Monad Improvement Proposals (MIPs) This MIP proposes two new opcodes that allow-fine grained introspection of the EVM call stack.

  2. @port #2 Oct 11, 2026, 6:12 AM

    This looks amazing, very well scoped! Though I might have one question here: The authorization section has a warning against using CALLERN(n > 0) for auth, so would a similar warning for CALLSTACKDEPTH help here? Because require(CALLSTACKDEPTH == 0) is almost like a direct call only flag and if im getting this right, would prevent users that come through a smart wallet or a router or multicall3. Here is how I would maybe add it under security considerations: Contracts SHOULD NOT restrict functionality based on CALLSTACKDEPTH, as this breaks composability with smart accounts and batching contracts. This is definitely not something about the proposal itself but just something that I had in mind while reading the spec.