Exercises — Module 5

Exercise Setup

git checkout -b my-day-5 my-day-4

Create stub file:

  • src/call.rsCallFrame, CallType, gas_to_forward()

Add pub mod call; to src/lib.rs.

Peek: git show day-5:src/call.rs | head -130

Compare: git diff day-5 -- src/call.rs

These exercises cover call frame construction, the depth limit, and the 63/64 gas-forwarding rule (EIP-150).

63/64 Rule (reference)

#![allow(unused)]
fn main() {
/// Calculate the gas to forward to a sub-call using the 63/64 rule (EIP-150).
///
/// The caller can specify a gas amount, but at most 63/64 of remaining gas
/// is forwarded. This prevents gas exhaustion attacks through deep call trees.
pub fn gas_to_forward(gas_remaining: u64, gas_requested: u64) -> u64 {
    let max_forward = gas_remaining - gas_remaining / 64; // 63/64 of remaining
    gas_requested.min(max_forward)
}
}

Exercise 5.1 — CallFrame construction

What to do. Construct one frame for each of CALL, DELEGATECALL, and STATICCALL using the provided constructors. For each frame, assert the correct call_type, code_address, context_address, caller, and is_static flag.

Key invariants to verify:

Typecontext_addressis_staticvalue
CALLsame as code_addressfalseany
DELEGATECALLcaller's addressfalsepreserved
STATICCALLsame as code_addresstrueU256::ZERO

How to verify.

cargo test ex_5_1

Hint. For DELEGATECALL the storage context belongs to the caller, not the callee. Pass context_address = caller_addr to new_delegatecall. Asserting frame.context_address != frame.code_address is a good extra check.

Rust pattern — enum variants with constructors. Each new_* function encodes the invariants for its call type. Using CallType::DelegateCall in a match later lets the compiler remind you to handle all variants.


Exercise 5.2 — Depth limit is exactly 1024

What to do. Assert that the exported constant MAX_CALL_DEPTH equals 1024. Also write a comment explaining why this limit exists.

How to verify.

cargo test ex_5_2

Hint. The 1025th call must fail with a call-depth exceeded error, not a stack overflow in the host language. The limit is a Yellow Paper protocol constant, not a Rust implementation detail.

Rust pattern — protocol constants. Naming constants with pub const in the module that owns their semantics (call.rs) keeps the definition co-located with the enforcement logic and makes them importable everywhere without repetition.


Exercise 5.4 — Gas forwarding follows the 63/64 rule

What to do. Call gas_to_forward with at least four cases and assert the results:

  1. gas_remaining = 6400, gas_requested = 100006300 (capped at 63/64)
  2. gas_remaining = 6400, gas_requested = 10001000 (requested is below cap)
  3. gas_remaining = 64, gas_requested = 100063
  4. gas_remaining = 1, gas_requested = 10001

How to verify.

cargo test ex_5_4

Hint. Integer division truncates in Rust: gas_remaining / 64 rounds down, so is always at least . The min call picks whichever is smaller: the cap or the request.

Rust pattern — min/max arithmetic. a.min(b) is idiomatic Rust for if a < b { a } else { b }. Prefer it over manual branches to keep the forwarding formula on one line and easy to audit against the Yellow Paper.


Yellow Paper Map

SymbolMeaningSection
ΘMessage call function§8
Gas stipend on the stack§8.1
EIP-15063/64 gas forwarding rule§8
nCall depth counter§8
MAX_CALL_DEPTHProtocol constant (1024)§8

Run all Module 5 tests: cargo test call