FORENSIAWeb3 forensicsOpen workspace

Who actually executed each step of a transaction

Each call now separates the transaction sender, the immediate caller, the requested code and the account whose storage is in play. These are interpreted roles, not owners or verified code.

· Forensia · 7 min read · Transactions

Every step inside a transaction has a From and a To. It is natural to read that as one party paying or instructing another. Often that reading is wrong.

A contract can borrow code from a different address and run it as if it were its own, using its own balance and its own stored data. Since EIP-7702, an ordinary wallet can do the same. In those steps the To address is where the code was requested from. It is not who received anything, and it is not whose data was in play.

Forensia now explains each call with four separate roles, so you can see who started the transaction, who made this particular call, which code was asked for, and whose account the call ran in.

#Plain languageFour roles that are easy to confuse

Think of cooking from a borrowed recipe in your own kitchen. The recipe belongs to someone else. The kitchen, the pantry and the food that results are yours. Nobody would say the recipe author cooked your dinner, or that your ingredients were delivered to them.

A delegated call (the EVM instruction DELEGATECALL) is the borrowed recipe. The code comes from the target address. The account whose balance and storage are used stays the same as before the call.

  • Transaction sender: the address that started the whole transaction.
  • Sender seen by the call: the address the running code sees as its immediate caller. Deep inside a transaction this is usually a contract, not the transaction sender.
  • Requested target: the address the call asked for code from.
  • Account context: the account whose storage and balance the call operates on. In an ordinary call this is the target. In a delegated call it is the parent.

A fifth item sits next to these: the native value the call carries. In a delegated call the value is inherited from the parent. It is the same amount seen again, not a second payment.

#How it worksReading the call context in the workspace

  1. Search an EVM transaction hash and, on Overview, open Inspect transaction steps.
  2. Find a step that is not a token event and open the disclosure titled Whose account does this call use?
  3. Read the fields: Account context · interpreted, Requested target · not verified code, Sender seen by this call · msg.sender, Transaction sender, Native call value · msg.value · wei, and Execution constraint.
  4. Read the paragraphs under the fields. They say whether the value was inherited, and they repeat the limits of the interpretation.
  5. In Activity, delegated and read-only internal calls are titled Delegated internal call or Read-only internal call. Their amount is labeled Call-context value and the destination is labeled Requested code target or Read-only target, not a payment recipient.

Two Ethereum review transactions from the engineering notes show the difference. In 0x22fc4a1bba0e2a711a5852f5c835eea4cb764f989895b1b48ec73d84dab8ed95, a read-only delegated call requesting code from 0x43506849D7C04F9138D1A2050bbF3A0c054402dd shows call sender 0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5, which is not the transaction sender 0xf7bAc63fc7CEaCf0589F25454Ecf5C2ce904997c, with an inherited value of zero.

In 0x946757737d6d893b52de97a07c176953edcb7a5e84c582b6ca399c54c2bcc3d7, the first nested call is a DELEGATECALL that keeps exactly 266000000000000000 wei (0.266 ETH) from the outer transaction. Its account and its requested target are the same address, 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45. Activity shows that 0.266 ETH as Call-context value. It is the outer payment seen from inside, not another 0.266 ETH.

#Technical detailHow each role is derived from the call tree

Inputs

The derivation reads the call tree returned by debug_traceTransaction with the callTracer, after the hierarchy has been validated from recorded child-index paths. It is computed for display only and is never written back into the captured evidence. In callTracer output, the from field of a frame is the calling execution account. It is not necessarily the code target of the parent frame, and DELEGATECALL is the case where the two differ.

The decision table

Call typeAccount contextSender seen (msg.sender)Call value (msg.value)
CALLThe frame’s to addressThe frame’s from addressThe frame’s recorded value
STATICCALLThe frame’s to address, read-onlyThe frame’s from addressZero; any other recorded value withholds the branch
DELEGATECALLInherited from the parentInherited from the parentThe parent’s value, shown only if this frame records the same exact value
CALLCODEInherited from the parentThe frame’s from addressThe frame’s own recorded value, not inherited
CREATE, CREATE2The new account addressThe frame’s from addressThe frame’s recorded value; no requested target is shown

The transaction sender is the from address of the root frame and is carried unchanged down every branch. The root must be a CALL or a CREATE.

Checks before a role is assigned

  • The frame’s from address must equal the interpreted account context of its parent. If it does not, the frame gets no context, and neither does anything beneath it.
  • A DELEGATECALL or CALLCODE needs a parent with an interpreted account. There is nothing to inherit otherwise.
  • Values must be canonical decimal uint256 strings below 2 to the power 256. There is no floating-point conversion. A missing value is shown as unavailable, never as zero.
  • If a DELEGATECALL frame and its parent do not both retain the same valid value, the value is unavailable, and it stays unavailable through further delegation. An ordinary CALL beneath it can still supply its own value.
  • Read-only status propagates from a STATICCALL to everything inside it. Within a read-only context, a CREATE, a CREATE2 or a CALL with nonzero value conflicts with the execution rules and the branch is withheld. A CALLCODE with nonzero value inside a read-only context is allowed, as EIP-214 specifies.

When the explanation is withheld entirely

New captures record the explanation version forensia.call-context.v1. That version applies only when all of the following hold. When one fails, the calls and events stay visible and only the role explanation is withheld.

RequirementDetail
NetworkEthereum mainnet, chain ID 1
Transaction and receipt typeBoth present, equal, and in the range 0 to 4
Historical rule contextThe captured block matches the pinned Ethereum reference schedule
Call sourceA captured geth-callTracer tree
DELEGATECALLBlock 1,150,000 or later (EIP-7)
STATICCALLBlock 4,370,000 or later (EIP-214)
CREATE2Block 7,280,000 or later (EIP-1014)

The opcode activation checks are applied per branch. A frame that predates its activation gets no context and passes none to its descendants; an unrelated sibling branch is unaffected. The pinned reference schedule has a fixed review horizon of 7 October 2026, 00:00 UTC. Blocks beyond it stay unknown until a new version is reviewed.

EIP-7702 and why the target is called requested

Account code of exactly 0xef0100 followed by a 20-byte address is classified as an EIP-7702 delegation designation: a pointer, not executable contract bytecode. An address lookup on such a wallet reports the delegation target on Overview. For a transaction, Forensia also asks eth_getCode for up to four distinct call targets using the EIP-1898 form {blockHash, requireCanonical: true}, keeping at most 49,152 bytes each. A pointer found this way is shown as Delegation pointer at block end with its destination. The destination is not followed any further.

That observation is the code at the end of the containing block. It is not the code at the start of the transaction or at the moment of the call. This is why the field is labeled as a requested target and never as the code that ran.

The Activity labels have their own gate

An activity row gets the Call-context value wording only when its ID internal:{hash}:{n} maps to exactly one step evm-flow:call:{hash}:{n} of type DELEGATECALL, CALLCODE or STATICCALL; the row and the step agree on participants, raw value, status, selector and source references; the interpreted call value equals the row’s raw value; every linked source was captured; and one of them is a matching callTracer request. Otherwise the row keeps the generic internal-call wording. Matching amounts, selectors, addresses or display order are never used as a substitute.

#LimitsWhat the call context does not tell you

  • It is interpreted from the call structure a provider reported. It is not an execution replay and not a storage diff.
  • The requested target is not verified code. Delegation, including EIP-7702, can redirect it, and the exact bytes that ran are not resolved.
  • An account context does not show that storage was written or that any change persisted. Failed and reverted calls keep their failure labels.
  • The sender seen by a call and the transaction sender are addresses. They do not identify a person, establish who owns an account, or show who signed a separate EIP-7702 authorization. Signatures are not verified.
  • The versioned explanation covers Ethereum mainnet only. On other supported networks the calls are shown without it.
  • Captures made before these fields existed are not upgraded.
  • One real nonzero DELEGATECALL has been checked end to end. CALLCODE and other nonzero cases are covered by isolated tests only, not by a live example.