Exercises — Module 5
Exercise Setup
Exercise Setup
git checkout -b my-day-5 my-day-4
Create stub file:
src/call.rs—CallFrame,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:
| Type | context_address | is_static | value |
|---|---|---|---|
CALL | same as code_address | false | any |
DELEGATECALL | caller's address | false | preserved |
STATICCALL | same as code_address | true | U256::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. UsingCallType::DelegateCallin amatchlater 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 constin 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:
gas_remaining = 6400,gas_requested = 10000→6300(capped at 63/64)gas_remaining = 6400,gas_requested = 1000→1000(requested is below cap)gas_remaining = 64,gas_requested = 1000→63gas_remaining = 1,gas_requested = 1000→1
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 forif 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
| Symbol | Meaning | Section |
|---|---|---|
| Θ | Message call function | §8 |
| Gas stipend on the stack | §8.1 | |
| EIP-150 | 63/64 gas forwarding rule | §8 |
n | Call depth counter | §8 |
MAX_CALL_DEPTH | Protocol constant (1024) | §8 |
Run all Module 5 tests: cargo test call