From 7377b85ba3817339d61cbc6ceb63f0eca0c605c0 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 12 Feb 2024 14:51:34 +0000 Subject: [PATCH 01/21] docs(yellowpaper): avm tree access operations --- yellow-paper/docs/public-vm/avm.md | 4 +- .../docs/public-vm/gen/_InstructionSet.mdx | 553 ++++++++++++------ yellow-paper/docs/public-vm/memory-model.md | 2 +- yellow-paper/docs/public-vm/state.md | 76 +-- yellow-paper/docs/public-vm/type-structs.md | 19 +- .../InstructionSet/InstructionSet.js | 299 ++++++++-- 6 files changed, 675 insertions(+), 278 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 6a955db92db..57d641614fd 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -48,7 +48,7 @@ The entirety of a contract's public code is represented as a single block of byt Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). ::: -Initialized by a contract call, an **execution context** includes the information necessary to initiate AVM execution along with all state maintained by the AVM throughout execution: +An **execution context** includes the information and state relevant to a contract call's execution. When a contract call is made, an execution context is initialized as specified in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls) sections. #### _AvmContext_ | Field | Type | @@ -62,7 +62,7 @@ Initialized by a contract call, an **execution context** includes the informatio ### Execution Environment -A context's **execution environment** remains constant throughout the context's execution. When a contract call initializes its execution context, it fully specifies the execution environment. +A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it fully specifies the execution environment. This is expanded on in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls) sections. #### _ExecutionEnvironment_ | Field | Type | Description | diff --git a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx index 051b43bcf29..102a56958b0 100644 --- a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx +++ b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx @@ -12,11 +12,10 @@ import CodeBlock from '@theme/CodeBlock' Click on an instruction name to jump to its section. - + - @@ -24,7 +23,6 @@ Click on an instruction name to jump to its section. - @@ -32,7 +30,6 @@ Click on an instruction name to jump to its section. - @@ -40,7 +37,6 @@ Click on an instruction name to jump to its section. - @@ -48,7 +44,6 @@ Click on an instruction name to jump to its section. - @@ -56,7 +51,6 @@ Click on an instruction name to jump to its section. - @@ -64,7 +58,6 @@ Click on an instruction name to jump to its section. - @@ -72,7 +65,6 @@ Click on an instruction name to jump to its section. - @@ -80,7 +72,6 @@ Click on an instruction name to jump to its section. - @@ -88,7 +79,6 @@ Click on an instruction name to jump to its section. - @@ -96,7 +86,6 @@ Click on an instruction name to jump to its section. - @@ -104,7 +93,6 @@ Click on an instruction name to jump to its section. - @@ -112,7 +100,6 @@ Click on an instruction name to jump to its section. - @@ -120,7 +107,6 @@ Click on an instruction name to jump to its section. - @@ -128,7 +114,6 @@ Click on an instruction name to jump to its section. - @@ -136,7 +121,6 @@ Click on an instruction name to jump to its section. - @@ -144,7 +128,6 @@ Click on an instruction name to jump to its section. - @@ -152,7 +135,6 @@ Click on an instruction name to jump to its section. - @@ -160,7 +142,6 @@ Click on an instruction name to jump to its section. - @@ -168,7 +149,6 @@ Click on an instruction name to jump to its section. - @@ -176,7 +156,6 @@ Click on an instruction name to jump to its section. - @@ -184,7 +163,6 @@ Click on an instruction name to jump to its section. - @@ -192,7 +170,6 @@ Click on an instruction name to jump to its section. - @@ -200,7 +177,6 @@ Click on an instruction name to jump to its section. - @@ -208,7 +184,6 @@ Click on an instruction name to jump to its section. - @@ -216,7 +191,6 @@ Click on an instruction name to jump to its section. - @@ -224,7 +198,6 @@ Click on an instruction name to jump to its section. - @@ -232,7 +205,6 @@ Click on an instruction name to jump to its section. - @@ -240,7 +212,6 @@ Click on an instruction name to jump to its section. - @@ -248,7 +219,6 @@ Click on an instruction name to jump to its section. - @@ -256,7 +226,6 @@ Click on an instruction name to jump to its section. - @@ -264,7 +233,6 @@ Click on an instruction name to jump to its section. - @@ -272,7 +240,6 @@ Click on an instruction name to jump to its section. - @@ -280,7 +247,6 @@ Click on an instruction name to jump to its section. - @@ -288,7 +254,6 @@ Click on an instruction name to jump to its section. - @@ -296,7 +261,6 @@ Click on an instruction name to jump to its section. - @@ -304,7 +268,6 @@ Click on an instruction name to jump to its section. - @@ -312,7 +275,6 @@ Click on an instruction name to jump to its section. - - @@ -329,7 +290,6 @@ context.machineState.pc = loc`} - @@ -337,7 +297,6 @@ context.machineState.pc = loc`} - @@ -345,79 +304,112 @@ context.machineState.pc = loc`} - - - - - + + + - - - - + + + - - - - + + + - - - - + + + - - - - + + + - - - - + + + - - - - + + + - + + + + + + - - + + + + + + - + - - + - - + - - + - @@ -334,7 +334,18 @@ M[existsOffset] = exists`} - + + + + + + - + - + - - + + - - + + - + - + - + - + - + + + + + + - + @@ -1676,20 +1688,25 @@ Call into another contract - **successOffset**: destination memory offset specifying where to store the call's success (0: failure, 1: success) - **Expression**: -{`M[successOffset] = call( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize])`} +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false, isDelegateCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} - **Details**: Creates a new (nested) execution context and triggers execution within that context. Execution proceeds in the nested context until it reaches a halt at which point execution resumes in the current/calling context. A non-existent contract or one with no code will return success. - The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./nested-calls) - section, which details nested context derivation, gas cost and refunds, handling - a call's results, and world state access tracing. + ["Nested contract calls"](./nested-calls) provides a full explanation of this + instruction along with the shorthand used in the expression above. + The explanation includes details on charging gas for nested calls, + nested context derivation, world state tracing, and updating the parent context + after the nested call halts. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` - **Tag updates**: @@ -1719,17 +1736,22 @@ Call into another contract, disallowing World State and Accrued Substate modific - **successOffset**: destination memory offset specifying where to store the call's success (0: failure, 1: success) - **Expression**: -{`M[successOffset] = staticcall( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize])`} +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=true, isDelegateCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} - **Details**: Same as `CALL`, but disallows World State and Accrued Substate modifications. - The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./nested-calls) - section, which details nested context derivation, gas cost and refunds, handling - a call's results, and world state access tracing. + ["Nested contract calls"](./nested-calls) provides a full explanation of this + instruction along with the shorthand used in the expression above. + The explanation includes details on charging gas for nested calls, + nested context derivation, world state tracing, and updating the parent context + after the nested call halts. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` - **Tag updates**: @@ -1759,18 +1781,23 @@ Call into another contract, but keep the caller's `sender` and `storageAddress` - **successOffset**: destination memory offset specifying where to store the call's success (0: failure, 1: success) - **Expression**: -{`M[successOffset] = delegatecall( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize])`} +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false, isDelegateCall=true) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`} - **Details**: Same as `CALL`, but `sender` and `storageAddress` remains the same in the nested call as they were in the caller. - The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./nested-calls) - section, which details nested context derivation, gas cost and refunds, handling - a call's results, and world state access tracing. + ["Nested contract calls"](./nested-calls) provides a full explanation of this + instruction along with the shorthand used in the expression above. + The explanation includes details on charging gas for nested calls, + nested context derivation, world state tracing, and updating the parent context + after the nested call halts. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` - **Tag updates**: diff --git a/yellow-paper/docs/public-vm/instruction-set.mdx b/yellow-paper/docs/public-vm/instruction-set.mdx index 196c11db8a7..f02a8693786 100644 --- a/yellow-paper/docs/public-vm/instruction-set.mdx +++ b/yellow-paper/docs/public-vm/instruction-set.mdx @@ -6,7 +6,7 @@ The following notes are relevant to the table and sections below: - `M[offset]` notation is shorthand for `context.machineState.memory[offset]` - `S[slot]` notation is shorthand for an access to the specified slot in the current contract's public storage (`context.worldState.publicStorage`) after the slot has been siloed by the storage address (`hash(context.environment.storageAddress, slot)`) - Any instruction whose description does not mention a program counter change simply increments it: `context.machineState.pc++` -- All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./execution#gas-limits-and-tracking) +- All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./execution#gas-checks-and-tracking) - Any instruction can lead to an exceptional halt as specified in ["Exceptional halting"](./execution#exceptional-halting) - The term `hash` used in expressions below represents a Poseidon hash operation. - Type structures used in world state tracing operations are defined in ["Type Definitions"](./type-structs) diff --git a/yellow-paper/docs/public-vm/nested-calls.mdx b/yellow-paper/docs/public-vm/nested-calls.mdx index f35eb6897c5..f4937081fd6 100644 --- a/yellow-paper/docs/public-vm/nested-calls.mdx +++ b/yellow-paper/docs/public-vm/nested-calls.mdx @@ -1,19 +1,39 @@ # Nested Contract Calls -A **nested contract call** occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), or [`DELEGATECALL`](./instruction-set#isa-section-delegatecall)). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. +A **nested contract call** occurs _during_ AVM execution and is triggered by a **contract call instruction**. The AVM [instruction set](./instruction-set) includes three contract call instructions: [`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), and [`DELEGATECALL`](./instruction-set#isa-section-delegatecall). -## Nested Call Instructions +A nested contract call performs the following operations: +1. [Charge gas](#gas-cost-of-call-instruction) for the nested call +1. [Trace the nested contract call](#tracing-nested-contract-calls) +1. [Derive the **nested context**](#context-initialization-for-nested-calls) from the calling context and the call instruction +1. Initiate [AVM execution](./execution) within the nested context until a halt is reached +1. [Update the **calling context**](#updating-the-calling-context-after-nested-call-halts) after the nested call halts -The AVM supports three types of contract calls: [`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), and [`DELEGATECALL`](./instruction-set#isa-section-delegatecall). +Or, in pseudocode: +```jsx +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } + +isStaticCall = instr.opcode == STATICCALL +isDelegateCall = instr.opcode == DELEGATECALL -These call instructions share the same argument definitions: `gasOffset`, `addrOffset`, `argsOffset`, `argsSize`, `retOffset`, `retSize`, and `successOffset`. These arguments will be referred to via those keywords below, and will often be used in conjunction with the `M[offset]` syntax which is shorthand for `context.machineState.memory[offset]`. +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall, isDelegateCall) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext) +``` -The terms `isStaticCall` and `isDelegateCall` used below simply refer to the call instruction's type (`instr.opcode == STATICCALL`, `instr.opcode == DELEGATECALL`). +These call instructions share the same argument definitions: `gasOffset`, `addrOffset`, `argsOffset`, `argsSize`, `retOffset`, `retSize`, and `successOffset` (defined in the [instruction set](./instruction-set)). These arguments will be referred to via those keywords below, and will often be used in conjunction with the `M[offset]` syntax which is shorthand for `context.machineState.memory[offset]`. ## Tracing nested contract calls Before nested execution begins, the contract call is traced. ```jsx +traceNestedCall(context, addrOffset) +// which is shorthand for context.worldStateAccessTrace.contractCalls.append( TracedContractCall { callPointer: context.worldStateAccessTrace.contractCalls.length + 1, @@ -36,11 +56,15 @@ import NestedContext from "./_nested-context.md"; A call instruction's gas cost is derived from its `gasOffset` argument. In other words, the caller "allocates" gas for a nested call via its `gasOffset` argument. -| Cost Term | Value | -| --- | --- | -| `l1GasCost` | `M[gasOffset]` | -| `l2GasCost` | `M[gasOffset+1]` | -| `daGasCost` | `M[gasOffset+2]` | +As with all instructions, gas is checked and cost is deducted _prior_ to the instruction's execution. +```jsx +chargeGas(context, + l1GasCost=M[gasOffset], + l2GasCost=M[gasOffset+1], + daGasCost=M[gasOffset+2]) +``` + +> The shorthand `chargeGas` is defined in ["Gas checks and tracking"](./execution#gas-checks-and-tracking). As with all instructions, gas is checked and cost is deducted _prior_ to the instruction's execution. ```jsx @@ -61,10 +85,16 @@ Once the nested call's context is initialized, execution within that context beg execute(nestedContext) ``` -Execution is detailed in ["Execution, Gas, Halting"](./execution). Note that execution mutates the nested context. +Execution (and the `execution` shorthand above) is detailed in ["Execution, Gas, Halting"](./execution). Note that execution mutates the nested context. ## Updating the calling context after nested call halts +After the nested call halts, the calling context is updated. The call's success is extracted, unused gas is refunded, output data can be copied to the caller's memory, world state and accrued substate are conditionally accepted, and the world state trace is updated. The following shorthand is used to refer to this process in the ["Instruction Set"](./instruction-set): + +```jsx +updateContextAfterNestedCall(context, instr.args, nestedContext) +``` + The caller checks whether the nested call succeeded, and places the answer in memory. ```jsx context.machineState.memory[instr.args.successOffset] = !nestedContext.results.reverted @@ -90,7 +120,7 @@ if !nestedContext.results.reverted: context.accruedSubstate.append(nestedContext.accruedSubstate) ``` -## Accepting nested call's World State access trace +### Accepting nested call's World State access trace If the nested call reverted, the caller initializes the "end-lifetime" of all world state accesses made within the nested call. ```jsx diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 0c6edd578aa..d86329ca6c0 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -22,10 +22,11 @@ const CALL_INSTRUCTION_ARGS = [ {"name": "successOffset", "description": "destination memory offset specifying where to store the call's success (0: failure, 1: success)", "type": "u8"}, ]; const CALL_INSTRUCTION_DETAILS = ` - The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./nested-calls) - section, which details nested context derivation, gas cost and refunds, handling - a call's results, and world state access tracing.`; + ["Nested contract calls"](./nested-calls) provides a full explanation of this + instruction along with the shorthand used in the expression above. + The explanation includes details on charging gas for nested calls, + nested context derivation, world state tracing, and updating the parent context + after the nested call halts.`; const INSTRUCTION_SET_RAW = [ { @@ -1118,11 +1119,15 @@ context.accruedSubstate.sentL2ToL1Messages.append( ], "Args": CALL_INSTRUCTION_ARGS, "Expression":` -M[successOffset] = call( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize]) +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false, isDelegateCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext) `, "Summary": "Call into another contract", "Details": `Creates a new (nested) execution context and triggers execution within that context. @@ -1145,11 +1150,15 @@ T[retOffset:retOffset+retSize] = field ], "Args": CALL_INSTRUCTION_ARGS, "Expression": ` -M[successOffset] = staticcall( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize]) +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=true, isDelegateCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext) `, "Summary": "Call into another contract, disallowing World State and Accrued Substate modifications", "Details": `Same as \`CALL\`, but disallows World State and Accrued Substate modifications. ` @@ -1169,11 +1178,15 @@ T[retOffset:retOffset+retSize] = field ], "Args": CALL_INSTRUCTION_ARGS, "Expression": ` -M[successOffset] = delegatecall( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize]) +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false, isDelegateCall=true) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext) `, "Summary": "Call into another contract, but keep the caller's `sender` and `storageAddress`", "Details": `Same as \`CALL\`, but \`sender\` and \`storageAddress\` remains
OpcodeNameSummaryBit-sizeExpressionOpcodeNameSummaryExpression
0x00 [`ADD`](#isa-section-add) Addition (a + b)128 { `M[dstOffset] = M[aOffset] + M[bOffset] mod 2^k` }
0x01 [`SUB`](#isa-section-sub) Subtraction (a - b)128 { `M[dstOffset] = M[aOffset] - M[bOffset] mod 2^k` }
0x02 [`MUL`](#isa-section-mul) Multiplication (a * b)128 { `M[dstOffset] = M[aOffset] * M[bOffset] mod 2^k` }
0x03 [`DIV`](#isa-section-div) Unsigned division (a / b)128 { `M[dstOffset] = M[aOffset] / M[bOffset]` }
0x04 [`EQ`](#isa-section-eq) Equality check (a == b)128 { `M[dstOffset] = M[aOffset] == M[bOffset] ? 1 : 0` }
0x05 [`LT`](#isa-section-lt) Less-than check (a < b)128 { `M[dstOffset] = M[aOffset] < M[bOffset] ? 1 : 0` }
0x06 [`LTE`](#isa-section-lte) Less-than-or-equals check (a <= b)128 { `M[dstOffset] = M[aOffset] <= M[bOffset] ? 1 : 0` }
0x07 [`AND`](#isa-section-and) Bitwise AND (a & b)128 { `M[dstOffset] = M[aOffset] AND M[bOffset]` }
0x08 [`OR`](#isa-section-or) Bitwise OR (a | b)128 { `M[dstOffset] = M[aOffset] OR M[bOffset]` }
0x09 [`XOR`](#isa-section-xor) Bitwise XOR (a ^ b)128 { `M[dstOffset] = M[aOffset] XOR M[bOffset]` }
0x0a [`NOT`](#isa-section-not) Bitwise NOT (inversion)96 { `M[dstOffset] = NOT M[aOffset]` }
0x0b [`SHL`](#isa-section-shl) Bitwise leftward shift (a << b)128 { `M[dstOffset] = M[aOffset] << M[bOffset]` }
0x0c [`SHR`](#isa-section-shr) Bitwise rightward shift (a >> b)128 { `M[dstOffset] = M[aOffset] >> M[bOffset]` }
0x0d [`CAST`](#isa-section-cast) Type cast96 { `M[dstOffset] = cast(M[aOffset])` }
0x0e [`ADDRESS`](#isa-section-address) Get the address of the currently executing l2 contract56 { `M[dstOffset] = context.environment.address` }
0x0f [`STORAGEADDRESS`](#isa-section-storageaddress) Get the _storage_ address of the currently executing context56 { `M[dstOffset] = context.environment.storageAddress` }
0x10 [`ORIGIN`](#isa-section-origin) Get the transaction's origination address56 { `M[dstOffset] = context.environment.origin` }
0x11 [`SENDER`](#isa-section-sender) Get the address of the sender (caller of the current context)56 { `M[dstOffset] = context.environment.sender` }
0x12 [`PORTAL`](#isa-section-portal) Get the address of the l1 portal contract56 { `M[dstOffset] = context.environment.portal` }
0x13 [`FEEPERL1GAS`](#isa-section-feeperl1gas) Get the fee to be paid per "L1 gas" - constant for entire transaction56 { `M[dstOffset] = context.environment.feePerL1Gas` }
0x14 [`FEEPERL2GAS`](#isa-section-feeperl2gas) Get the fee to be paid per "L2 gas" - constant for entire transaction56 { `M[dstOffset] = context.environment.feePerL2Gas` }
0x15 [`FEEPERDAGAS`](#isa-section-feeperdagas) Get the fee to be paid per "DA gas" - constant for entire transaction56 { `M[dstOffset] = context.environment.feePerDaGas` }
0x16 [`CONTRACTCALLDEPTH`](#isa-section-contractcalldepth) Get how many contract calls deep the current call context is56 { `M[dstOffset] = context.environment.contractCallDepth` }
0x17 [`CHAINID`](#isa-section-chainid) Get this rollup's L1 chain ID56 { `M[dstOffset] = context.environment.globals.chainId` }
0x18 [`VERSION`](#isa-section-version) Get this rollup's L2 version ID56 { `M[dstOffset] = context.environment.globals.version` }
0x19 [`BLOCKNUMBER`](#isa-section-blocknumber) Get this L2 block's number56 { `M[dstOffset] = context.environment.globals.blocknumber` }
0x1a [`TIMESTAMP`](#isa-section-timestamp) Get this L2 block's timestamp56 { `M[dstOffset] = context.environment.globals.timestamp` }
0x1b [`COINBASE`](#isa-section-coinbase) Get the block's beneficiary address56 { `M[dstOffset] = context.environment.globals.coinbase` }
0x1c [`BLOCKL1GASLIMIT`](#isa-section-blockl1gaslimit) Total amount of "L1 gas" that a block can consume56 { `M[dstOffset] = context.environment.globals.l1GasLimit` }
0x1d [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) Total amount of "L2 gas" that a block can consume56 { `M[dstOffset] = context.environment.globals.l2GasLimit` }
0x1e [`BLOCKDAGASLIMIT`](#isa-section-blockdagaslimit) Total amount of "DA gas" that a block can consume56 { `M[dstOffset] = context.environment.globals.daGasLimit` }
0x1f [`CALLDATACOPY`](#isa-section-calldatacopy) Copy calldata into memory120 { `M[dstOffset:dstOffset+copySize] = context.environment.calldata[cdOffset:cdOffset+copySize]` }
0x20 [`L1GASLEFT`](#isa-section-l1gasleft) Remaining "L1 gas" for this call (after this instruction)56 { `M[dstOffset] = context.machineState.l1GasLeft` }
0x21 [`L2GASLEFT`](#isa-section-l2gasleft) Remaining "L2 gas" for this call (after this instruction)56 { `M[dstOffset] = context.MachineState.l2GasLeft` }
0x22 [`DAGASLEFT`](#isa-section-dagasleft) Remaining "DA gas" for this call (after this instruction)56 { `M[dstOffset] = context.machineState.daGasLeft` }
0x23 [`JUMP`](#isa-section-jump) Jump to a location in the bytecode48 { `context.machineState.pc = loc` }
0x24 [`JUMPI`](#isa-section-jumpi) Conditionally jump to a location in the bytecode88 { `context.machineState.pc = M[condOffset] > 0 ? loc : context.machineState.pc` }
0x25 [`INTERNALCALL`](#isa-section-internalcall) Make an internal call. Push the current PC to the internal call stack and jump to the target location.48 {`context.machineState.internalCallStack.push(context.machineState.pc) context.machineState.pc = loc`} @@ -321,7 +283,6 @@ context.machineState.pc = loc`}
0x26 [`INTERNALRETURN`](#isa-section-internalreturn) Return from an internal call. Pop from the internal call stack and jump to the popped location.16 { `context.machineState.pc = context.machineState.internalCallStack.pop()` }
0x27 [`SET`](#isa-section-set) Set a memory word from a constant in the bytecode64+N { `M[dstOffset] = const` }
0x28 [`MOV`](#isa-section-mov) Move a word from source memory location to destination88 { `M[dstOffset] = M[srcOffset]` }
0x29 [`CMOV`](#isa-section-cmov) Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`)152 { `M[dstOffset] = M[condOffset] > 0 ? M[aOffset] : M[bOffset]` }
0x2a [`BLOCKHEADERBYNUM`](#isa-section-blockheaderbynum)Get the block header as of the specified block number88{ - `M[dstOffset:dstOffset+BLOCK_HEADER_LENGTH] = context.worldState.blockHeader[M[blockNumOffset]]` - }0x2a [`SLOAD`](#isa-section-sload)Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots. +{`M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]]`} +
0x2b [`SLOAD`](#isa-section-sload)Load a word from storage88{ - `M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress, M[slotOffset]]` - }0x2b [`SSTORE`](#isa-section-sstore)Write a word to this contract's persistent public storage +{`context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] = M[srcOffset]`} +
0x2c [`SSTORE`](#isa-section-sstore)Write a word to storage88{ - `context.worldState.publicStorage[context.environment.storageAddress, M[slotOffset]] = M[srcOffset]` - }0x2c [`NOTEHASHEXISTS`](#isa-section-notehashexists)Check whether a note hash exists in the note hash tree (as of the start of the current block) +{`exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.storageAddress, M[leafOffset]), +}) +M[existsOffset] = exists`} +
0x2d [`READL1TOL2MSG`](#isa-section-readl1tol2msg)Reads an L1-to-L2 message120{ - `M[dstOffset:dstOffset+msgSize] = context.worldState.l1ToL2Messages(M[msgKeyOffset])` - }0x2d [`EMITNOTEHASH`](#isa-section-emitnotehash)Emit a new note hash to be inserted into the note hash tree +{`context.worldState.noteHashes.append( + hash(context.environment.storageAddress, M[noteHashOffset]) +)`} +
0x2e [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg)Send an L2-to-L1 message88{ - `context.worldState.l2ToL1Messages.append(M[msgOffset:msgOffset+msgSize])` - }0x2e [`NULLIFIEREXISTS`](#isa-section-nullifierexists)Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block) +{`exists = context.worldState.nullifiers.has( + hash(context.environment.storageAddress, M[nullifierOffset]) +) +M[existsOffset] = exists`} +
0x2f [`EMITNOTEHASH`](#isa-section-emitnotehash)Emit a new note hash to be inserted into the notes tree56{ - `context.worldState.newHashes.append(M[noteHashOffset])` - }0x2f [`EMITNULLIFIER`](#isa-section-emitnullifier)Emit a new nullifier to be inserted into the nullifier tree +{`context.worldState.nullifiers.append( + hash(context.environment.storageAddress, M[nullifierOffset]) +)`} +
0x30 [`EMITNULLIFIER`](#isa-section-emitnullifier)Emit a new nullifier to be inserted into the nullifier tree56{ - `context.worldState.nullifiers.append(M[nullifierOffset])` - }0x30 [`READL1TOL2MSG`](#isa-section-readl1tol2msg)Check if a message exists in the L1-to-L2 message tree and reads it if so. +{`exists = context.worldState.l1ToL2Messages.has({ + leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] +}) +M[existsOffset] = exists +if exists: + M[dstOffset:dstOffset+msgSize] = context.worldState.l1ToL2Messages.get({ + leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] + })`} +
0x31 [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog)0x31 [`HEADERMEMBER`](#isa-section-headermember)Retrieve one member from a specified block's header. Revert if header does not yet exist. See ["Archive"](../state/archive) for more. +{`M[dstOffset] = context.worldState.headers.get(M[blockIndexOffset])[M[memberIndexOffset]]`} +
0x32 [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog) Emit an unencrypted log88{ - `context.accruedSubstate.unencryptedLogs.append(M[logOffset:logOffset+logSize])` - } +{`context.accruedSubstate.unencryptedLogs.append( + UnencryptedLog { + address: context.environment.address, + log: M[logOffset:logOffset+logSize], + } +)`} +
0x33 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg)Send an L2-to-L1 message +{`context.accruedSubstate.sentL2ToL1Messages.append( + SentL2ToL1Message { + address: context.environment.address, + portal: context.environment.portal, + message: M[msgOffset:msgOffset+msgSize] + } +)`} +
0x32 [`CALL`](#isa-section-call)0x34 [`CALL`](#isa-section-call) Call into another contract248 {`M[successOffset] = call( M[gasOffset], M[gasOffset+1], M[gasOffset+2], @@ -427,9 +419,8 @@ context.machineState.pc = loc`}
0x33 [`STATICCALL`](#isa-section-staticcall)0x35 [`STATICCALL`](#isa-section-staticcall) Call into another contract, disallowing World State and Accrued Substate modifications248 {`M[successOffset] = staticcall( M[gasOffset], M[gasOffset+1], M[gasOffset+2], @@ -439,18 +430,16 @@ context.machineState.pc = loc`}
0x34 [`RETURN`](#isa-section-return)0x36 [`RETURN`](#isa-section-return) Halt execution within this context (without revert), optionally returning some data88 {`context.contractCallResults.output = M[retOffset:retOffset+retSize] halt`}
0x35 [`REVERT`](#isa-section-revert)0x37 [`REVERT`](#isa-section-revert) Halt execution within this context as `reverted`, optionally returning some data88 {`context.contractCallResults.output = M[retOffset:retOffset+retSize] context.contractCallResults.reverted = true @@ -471,7 +460,7 @@ Addition (a + b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -492,7 +481,7 @@ Subtraction (a - b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -513,7 +502,7 @@ Multiplication (a * b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -534,7 +523,7 @@ Unsigned division (a / b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -555,7 +544,7 @@ Equality check (a == b) - **Category**: Compute - Comparators - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -576,7 +565,7 @@ Less-than check (a < b) - **Category**: Compute - Comparators - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -597,7 +586,7 @@ Less-than-or-equals check (a <= b) - **Category**: Compute - Comparators - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -618,7 +607,7 @@ Bitwise AND (a & b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -639,7 +628,7 @@ Bitwise OR (a | b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -660,7 +649,7 @@ Bitwise XOR (a ^ b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -681,7 +670,7 @@ Bitwise NOT (inversion) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's input - **dstOffset**: memory offset specifying where to store operation's result @@ -701,7 +690,7 @@ Bitwise leftward shift (a << b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -722,7 +711,7 @@ Bitwise rightward shift (a >> b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -743,12 +732,12 @@ Type cast - **Category**: Type Conversions - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **dstTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against. + - **dstTag**: The [tag/size](./state-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against. - **Args**: - **aOffset**: memory offset of word to cast - **dstOffset**: memory offset specifying where to store operation's result - **Expression**: `M[dstOffset] = cast(M[aOffset])` -- **Details**: Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./memory-model#cast-and-tag-conversions) for more details. +- **Details**: Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./state-model#cast-and-tag-conversions) for more details. - **Tag updates**: `T[dstOffset] = dstTag` - **Bit-size**: 96 @@ -1188,7 +1177,7 @@ Set a memory word from a constant in the bytecode - **Category**: Machine State - Memory - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [type/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET. + - **inTag**: The [type/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET. - **Args**: - **const**: an N-bit constant value from the bytecode to store in memory (any type except `field`) - **dstOffset**: memory offset specifying where to store the constant @@ -1238,152 +1227,362 @@ Move a word (conditionally chosen) from one memory location to another (`d = con [![](./images/bit-formats/CMOV.png)](./images/bit-formats/CMOV.png) -### `BLOCKHEADERBYNUM` -Get the block header as of the specified block number - -[See in table.](#isa-table-blockheaderbynum) - -- **Opcode**: 0x2a -- **Category**: World State -- **Flags**: - - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. -- **Args**: - - **blockNumOffset**: memory offset of the block number input - - **dstOffset**: memory offset specifying where to store operation's result's 0th word -- **Expression**: `M[dstOffset:dstOffset+BLOCK_HEADER_LENGTH] = context.worldState.blockHeader[M[blockNumOffset]]` -- **Tag updates**: `T[dstOffset:dstOffset+BLOCK_HEADER_LENGTh] = field` -- **Bit-size**: 88 - -[![](./images/bit-formats/BLOCKHEADERBYNUM.png)](./images/bit-formats/BLOCKHEADERBYNUM.png) - ### `SLOAD` -Load a word from storage +Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots. [See in table.](#isa-table-sload) -- **Opcode**: 0x2b +- **Opcode**: 0x2a - **Category**: World State - Public Storage - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - **slotOffset**: memory offset of the storage slot to load from - **dstOffset**: memory offset specifying where to store operation's result -- **Expression**: `M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress, M[slotOffset]]` -- **Details**: Load a word from this contract's persistent public storage into memory. +- **Expression**: + +{`M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]]`} + +- **Details**: + +{`// Expression is short-hand for +leafIndex = hash(context.environment.storageAddress, M[slotOffset]) +exists = context.worldState.publicStorage.has(leafIndex) // exists == previously-written +if exists: + value = context.worldState.publicStorage.get(leafIndex: leafIndex) +else: + value = 0 +M[dstOffset] = value`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.publicStorageReads.append( + TracedStorageRead { + callPointer: context.environment.callPointer, + slot: M[slotOffset], + exists: exists, // defined above + value: value, // defined above + counter: clk, + } +)`} + +- **Triggers downstream circuit operations**: Storage slot siloing (hash with contract address), public data tree membership check - **Tag updates**: `T[dstOffset] = field` - **Bit-size**: 88 [![](./images/bit-formats/SLOAD.png)](./images/bit-formats/SLOAD.png) ### `SSTORE` -Write a word to storage +Write a word to this contract's persistent public storage [See in table.](#isa-table-sstore) -- **Opcode**: 0x2c +- **Opcode**: 0x2b - **Category**: World State - Public Storage - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - **srcOffset**: memory offset of the word to store - **slotOffset**: memory offset containing the storage slot to store to -- **Expression**: `context.worldState.publicStorage[context.environment.storageAddress, M[slotOffset]] = M[srcOffset]` -- **Details**: Store a word from memory into this contract's persistent public storage. +- **Expression**: + +{`context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] = M[srcOffset]`} + +- **Details**: + +{`// Expression is short-hand for +context.worldState.publicStorage.set({ + leafIndex: hash(context.environment.storageAddress, M[slotOffset]), + leaf: M[srcOffset], +})`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.publicStorageWrites.append( + TracedStorageWrite { + callPointer: context.environment.callPointer, + slot: M[slotOffset], + value: M[srcOffset], + counter: clk, + } +)`} + +- **Triggers downstream circuit operations**: Storage slot siloing (hash with contract address), public data tree update - **Bit-size**: 88 [![](./images/bit-formats/SSTORE.png)](./images/bit-formats/SSTORE.png) -### `READL1TOL2MSG` -Reads an L1-to-L2 message +### `NOTEHASHEXISTS` +Check whether a note hash exists in the note hash tree (as of the start of the current block) -[See in table.](#isa-table-readl1tol2msg) +[See in table.](#isa-table-notehashexists) -- **Opcode**: 0x2d -- **Category**: World State - Messaging +- **Opcode**: 0x2c +- **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - - **msgKeyOffset**: memory offset of the message's key - - **dstOffset**: memory offset to place the 0th word of the message content - - **msgSize**: number of words in the message -- **Expression**: `M[dstOffset:dstOffset+msgSize] = context.worldState.l1ToL2Messages(M[msgKeyOffset])` -- **Tag updates**: `T[dstOffset:dstOffset+msgSize] = field` + - **leafOffset**: memory offset of the leaf + - **leafIndexOffset**: memory offset of the leaf index + - **existsOffset**: memory offset specifying where to store operation's result (whether the archive leaf exists) +- **Expression**: + +{`exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.storageAddress, M[leafOffset]), +}) +M[existsOffset] = exists`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.noteHashChecks.append( + TracedLeafCheck { + callPointer: context.environment.callPointer, + leafIndex: M[leafIndexOffset] + leaf: M[leafOffset], + exists: exists, // defined above + counter: clk, + } +)`} + +- **Triggers downstream circuit operations**: Note hash siloing (hash with storage contract address), note hash tree membership check +- **Tag updates**: `T[dstOffset] = u8` - **Bit-size**: 120 -[![](./images/bit-formats/READL1TOL2MSG.png)](./images/bit-formats/READL1TOL2MSG.png) -### `SENDL2TOL1MSG` -Send an L2-to-L1 message +### `EMITNOTEHASH` +Emit a new note hash to be inserted into the note hash tree -[See in table.](#isa-table-sendl2tol1msg) +[See in table.](#isa-table-emitnotehash) -- **Opcode**: 0x2e -- **Category**: World State - Messaging +- **Opcode**: 0x2d +- **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - - **msgOffset**: memory offset of the message content - - **msgSize**: number of words in the message -- **Expression**: `context.worldState.l2ToL1Messages.append(M[msgOffset:msgOffset+msgSize])` -- **Bit-size**: 88 + - **noteHashOffset**: memory offset of the note hash +- **Expression**: + +{`context.worldState.noteHashes.append( + hash(context.environment.storageAddress, M[noteHashOffset]) +)`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.newNoteHashes.append( + TracedNoteHash { + callPointer: context.environment.callPointer, + value: M[noteHashOffset], // unsiloed note hash + counter: clk, + } +)`} + +- **Triggers downstream circuit operations**: Note hash siloing (hash with contract address), note hash tree insertion. +- **Bit-size**: 56 -[![](./images/bit-formats/SENDL2TOL1MSG.png)](./images/bit-formats/SENDL2TOL1MSG.png) +[![](./images/bit-formats/EMITNOTEHASH.png)](./images/bit-formats/EMITNOTEHASH.png) -### `EMITNOTEHASH` -Emit a new note hash to be inserted into the notes tree +### `NULLIFIEREXISTS` +Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block) -[See in table.](#isa-table-emitnotehash) +[See in table.](#isa-table-nullifierexists) -- **Opcode**: 0x2f +- **Opcode**: 0x2e - **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - - **noteHashOffset**: memory offset of the note hash -- **Expression**: `context.worldState.newHashes.append(M[noteHashOffset])` -- **Bit-size**: 56 + - **nullifierOffset**: memory offset of the unsiloed nullifier + - **existsOffset**: memory offset specifying where to store operation's result (whether the nullifier exists) +- **Expression**: + +{`exists = context.worldState.nullifiers.has( + hash(context.environment.storageAddress, M[nullifierOffset]) +) +M[existsOffset] = exists`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.nullifierChecks.append( + TracedIndexedLeafCheck { + callPointer: context.environment.callPointer, + leaf: M[nullifierOffset], + exists: exists, // defined above + counter: clk, + } +)`} + +- **Triggers downstream circuit operations**: Nullifier siloing (hash with storage contract address), nullifier tree membership check +- **Tag updates**: `T[dstOffset] = u8` +- **Bit-size**: 88 -[![](./images/bit-formats/EMITNOTEHASH.png)](./images/bit-formats/EMITNOTEHASH.png) ### `EMITNULLIFIER` Emit a new nullifier to be inserted into the nullifier tree [See in table.](#isa-table-emitnullifier) -- **Opcode**: 0x30 +- **Opcode**: 0x2f - **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - **nullifierOffset**: memory offset of nullifier -- **Expression**: `context.worldState.nullifiers.append(M[nullifierOffset])` +- **Expression**: + +{`context.worldState.nullifiers.append( + hash(context.environment.storageAddress, M[nullifierOffset]) +)`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.newNullifiers.append( + TracedNullifier { + callPointer: context.environment.callPointer, + value: M[nullifierOffset], // unsiloed nullifier + counter: clk, + } +)`} + +- **Triggers downstream circuit operations**: Nullifier siloing (hash with contract address), nullifier tree non-membership-check and insertion. - **Bit-size**: 56 [![](./images/bit-formats/EMITNULLIFIER.png)](./images/bit-formats/EMITNULLIFIER.png) +### `READL1TOL2MSG` +Check if a message exists in the L1-to-L2 message tree and reads it if so. + +[See in table.](#isa-table-readl1tol2msg) + +- **Opcode**: 0x30 +- **Category**: World State - Messaging +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **msgKeyOffset**: memory offset of the message's key + - **msgLeafIndex**: memory offset of the message's leaf index in the L1-to-L2 message tree + - **existsOffset**: memory offset specifying where to store operation's result (whether the message exists in the L1-to-L2 message tree) + - **dstOffset**: memory offset to place the 0th word of the message content + - **msgSize**: number of words in the message +- **Expression**: + +{`exists = context.worldState.l1ToL2Messages.has({ + leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] +}) +M[existsOffset] = exists +if exists: + M[dstOffset:dstOffset+msgSize] = context.worldState.l1ToL2Messages.get({ + leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] + })`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.l1ToL2MessagesReads.append( + ReadL1ToL2Message { + callPointer: context.environment.callPointer, + portal: context.environment.portal, + leafIndex: M[msgLeafIndex], + msgKey: M[msgKeyOffset], + exists: exists, // defined above + } +)`} + +- **Additional AVM circuit checks**: `msgKey == sha256_to_field(msg)` +- **Triggers downstream circuit operations**: L1-to-L2 message tree membership check +- **Tag updates**: `T[dstOffset:dstOffset+msgSize] = field` +- **Bit-size**: 184 + +[![](./images/bit-formats/READL1TOL2MSG.png)](./images/bit-formats/READL1TOL2MSG.png) + +### `HEADERMEMBER` +Retrieve one member from a specified block's header. Revert if header does not yet exist. See ["Archive"](../state/archive) for more. + +[See in table.](#isa-table-headermember) + +- **Opcode**: 0x31 +- **Category**: World State - Archive Tree & Headers +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **blockIndexOffset**: memory offset of the block index (same as archive tree leaf index) of the header to access + - **memberIndexOffset**: memory offset of the index of the member to retrieve from the header of the specified block + - **dstOffset**: memory offset specifying where to store operation's result (the retrieved header member) +- **Expression**: + +{`M[dstOffset] = context.worldState.headers.get(M[blockIndexOffset])[M[memberIndexOffset]]`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.archiveChecks.append( + TracedArchiveLeafCheck { + leafIndex: M[blockIndexOffset], // leafIndex == blockIndex + leaf: hash(context.worldState.headers.get(M[blockIndexOffset])), + } +)`} + +- **Additional AVM circuit checks**: Hashes entire header to archive leaf for tracing. Aggregates header accesses and so that a header need only be hashed once. +- **Triggers downstream circuit operations**: Archive tree membership check +- **Tag updates**: `T[dstOffset] = field` +- **Bit-size**: 120 + + ### `EMITUNENCRYPTEDLOG` Emit an unencrypted log [See in table.](#isa-table-emitunencryptedlog) -- **Opcode**: 0x31 +- **Opcode**: 0x32 - **Category**: Accrued Substate - Logging - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - **logOffset**: memory offset of the data to log - **logSize**: number of words to log -- **Expression**: `context.accruedSubstate.unencryptedLogs.append(M[logOffset:logOffset+logSize])` +- **Expression**: + +{`context.accruedSubstate.unencryptedLogs.append( + UnencryptedLog { + address: context.environment.address, + log: M[logOffset:logOffset+logSize], + } +)`} + - **Bit-size**: 88 [![](./images/bit-formats/EMITUNENCRYPTEDLOG.png)](./images/bit-formats/EMITUNENCRYPTEDLOG.png) +### `SENDL2TOL1MSG` +Send an L2-to-L1 message + +[See in table.](#isa-table-sendl2tol1msg) + +- **Opcode**: 0x33 +- **Category**: Accrued Substate - Messaging +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **msgOffset**: memory offset of the message content + - **msgSize**: number of words in the message +- **Expression**: + +{`context.accruedSubstate.sentL2ToL1Messages.append( + SentL2ToL1Message { + address: context.environment.address, + portal: context.environment.portal, + message: M[msgOffset:msgOffset+msgSize] + } +)`} + +- **Bit-size**: 88 + +[![](./images/bit-formats/SENDL2TOL1MSG.png)](./images/bit-formats/SENDL2TOL1MSG.png) + ### `CALL` Call into another contract [See in table.](#isa-table-call) -- **Opcode**: 0x32 +- **Opcode**: 0x34 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1421,7 +1620,7 @@ Call into another contract, disallowing World State and Accrued Substate modific [See in table.](#isa-table-staticcall) -- **Opcode**: 0x33 +- **Opcode**: 0x35 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1457,7 +1656,7 @@ Halt execution within this context (without revert), optionally returning some d [See in table.](#isa-table-return) -- **Opcode**: 0x34 +- **Opcode**: 0x36 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1479,7 +1678,7 @@ Halt execution within this context as `reverted`, optionally returning some data [See in table.](#isa-table-revert) -- **Opcode**: 0x35 +- **Opcode**: 0x37 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. diff --git a/yellow-paper/docs/public-vm/memory-model.md b/yellow-paper/docs/public-vm/memory-model.md index d6087cba3e8..28f65c03de2 100644 --- a/yellow-paper/docs/public-vm/memory-model.md +++ b/yellow-paper/docs/public-vm/memory-model.md @@ -1,6 +1,6 @@ # Memory State Model -The goal of this note is to describe the VM state model and to specify "internal" VM abstractions that can be mapped to circuit designs. +This section describes the AVM memory model, and in particular specifies "internal" VM abstractions that can be mapped to the VM's circuit architecture. ## A memory-only state model diff --git a/yellow-paper/docs/public-vm/state.md b/yellow-paper/docs/public-vm/state.md index 005f90efa67..9f4a0df5d15 100644 --- a/yellow-paper/docs/public-vm/state.md +++ b/yellow-paper/docs/public-vm/state.md @@ -10,27 +10,28 @@ This section describes the types of state maintained by the AVM. | Field | Type | Description | | --- | --- | --- | -| `l1GasLeft` | `field` | Tracks the amount of L1 gas remaining at any point during execution. | -| `l2GasLeft` | `field` | Tracks the amount of L2 gas remaining at any point during execution. | -| `daGasLeft` | `field` | Tracks the amount of DA gas remaining at any point during execution. | -| `pc` | `field` | Index into the contract's bytecode indicating which instruction to execute. Initialized\* to 0. | -| `internalCallStack` | `Vector` | A stack of program counters pushed to and popped from by `INTERNALCALL` and `INTERNALRETURN` instructions. Initialized\* as empty. | -| `memory` | `[field; 2^32]` | A $2^{32}$ entry memory space accessible by user code (bytecode instructions). All 2^32 entries are initialized\* to 0. See ["Memory Model"](./memory-model) for a complete description of AVM memory. | +| `l1GasLeft` | `field` | Tracks the amount of L1 gas remaining at any point during execution. Initialized from contract call arguments. | +| `l2GasLeft` | `field` | Tracks the amount of L2 gas remaining at any point during execution. Initialized from contract call arguments. | +| `daGasLeft` | `field` | Tracks the amount of DA gas remaining at any point during execution. Initialized from contract call arguments. | +| `pc` | `field` | Index into the contract's bytecode indicating which instruction to execute. Initialized to 0 during context initialization. | +| `internalCallStack` | `Vector` | A stack of program counters pushed to and popped from by `INTERNALCALL` and `INTERNALRETURN` instructions. Initialized as empty during context initialization. | +| `memory` | `[field; 2^32]` | A $2^{32}$ entry memory space accessible by user code (bytecode instructions). All $2^{32}$ entries are assigned default value 0 during context initialization. See ["Memory Model"](./memory-model) for a complete description of AVM memory. | ## World State ### AVM's access to Aztec State -[Aztec's global state](../state) is implemented as a few merkle trees that are exposed to the AVM as follows: -| State | Tree | Tree Type | AVM Access | -| --- | --- | --- | --- | -| Note Hashes | Note Hash Tree | Append-only merkle tree | membership-checks (start-of-block), appends | -| Nullifiers | Nullifier Tree | Indexed merkle tree | membership-checks (latest), appends | -| L1-to-L2 Messages | L1-to-L2 Message Tree | Append-only (or indexed?) merkle tree | membership-checks (start-of-block), leaf-preimage-reads | -| Public Storage | Public Data Tree | Updatable merkle tree | membership-checks (latest), reads, writes | -| Headers | Archive Tree | Append-only merkle tree | membership-checks, leaf-preimage-reads | +[Aztec's global state](../state) is implemented as a few merkle trees. These trees are exposed to the AVM as follows: -> As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. The `contractClasses` interface provides access to contract classes indexed by class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. The `contractInstances` interface provides access to contract instances indexed by contract address. +| State | Tree | Merkle Tree Type | AVM Access | +| --- | --- | --- | --- | +| Public Storage | Public Data Tree | Updatable | membership-checks (latest), reads, writes | +| Note Hashes | Note Hash Tree | Append-only | membership-checks (start-of-block), appends | +| Nullifiers | Nullifier Tree | Indexed | membership-checks (latest), appends | +| L1-to-L2 Messages | L1-to-L2 Message Tree | Append-only | membership-checks (start-of-block), leaf-preimage-reads | +| Headers | Archive Tree | Append-only | membership-checks, leaf-preimage-reads | + +> As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. ### AVM World State @@ -42,13 +43,13 @@ As the AVM executes contract code, instructions may read or modify the world sta The following table defines an AVM context's world state interface: -| Field | AVM Instructions & Access | -| --- | --- | -| `noteHashes` | `NOTEHASHEXISTS` (membership-checks (start-of-block)), `EMITNULLIFIER` (appends) | -| `nullifiers` | `NULLIFIERSEXISTS` membership-checks (latest), `EMITNULLIFIER` (appends) | -| `l1ToL2Messages` | `READL1TOL2MSG` (membership-checks (start-of-block) & leaf-preimage-reads) | -| `publicStorage` | `SLOAD` (membership-checks (latest) & reads), `SSTORE` (writes) | -| `headers` | `HEADERMEMBER` (membership-checks & leaf-preimage-reads) | +| Field | AVM Instructions & Access | +| --- | --- | +| `publicStorage` | [`SLOAD`](./instruction-set#isa-section-sload) (membership-checks (latest) & reads), [`SSTORE`](./instruction-set#isa-section-sstore) (writes) | +| `noteHashes` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) (membership-checks (start-of-block)), [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) (appends) | +| `nullifiers` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) membership-checks (latest), [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) (appends) | +| `l1ToL2Messages` | [`READL1TOL2MSG`](./instruction-set#isa-section-readl1tol2msg) (membership-checks (start-of-block) & leaf-preimage-reads) | +| `headers` | [`HEADERMEMBER`](./instruction-set#isa-section-headermember) (membership-checks & leaf-preimage-reads) | ### World State Access Trace @@ -64,19 +65,19 @@ Each entry in the world state access trace is listed below along with its type a | Trace | Relevant State | Trace Vector Type | Instructions | | --- | --- | --- | --- | -| `publicStorageReads` | Public Storage | `Vector` | `SLOAD` | -| `publicStorageWrites` | Public Storage | `Vector` | `SSTORE` | -| `l1ToL2MessageReads` | L1-To-L2 Messages | `Vector` | `READL1TOL2MSG` | -| `noteHashChecks` | Note Hashes | `Vector` | `NOTEHASHEXISTS` | -| `newNoteHashes` | Note Hashes | `Vector` | `EMITNOTEHASH` | -| `nullifierChecks` | Nullifiers | `Vector` | `NULLIFIEREXISTS` | -| `newNullifiers` | Nullifiers | `Vector` | `EMITNULLIFIER` | -| `archiveChecks` | Headers | `Vector` | `HEADERMEMBER` | -| `contractCalls` | - | `Vector` | `*CALL` | +| `publicStorageReads` | Public Storage | `Vector` | [`SLOAD`](./instruction-set#isa-section-sload) | +| `publicStorageWrites` | Public Storage | `Vector` | [`SSTORE`](./instruction-set#isa-section-sstore) | +| `noteHashChecks` | Note Hashes | `Vector` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) | +| `newNoteHashes` | Note Hashes | `Vector` | [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) | +| `nullifierChecks` | Nullifiers | `Vector` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) | +| `newNullifiers` | Nullifiers | `Vector` | [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) | +| `l1ToL2MessageReads` | L1-To-L2 Messages | `Vector` | [`READL1TOL2MSG`](./instruction-set#isa-section-readl1tol2msg) | +| `archiveChecks` | Headers | `Vector` | [`HEADERMEMBER`](./instruction-set#isa-section-headermember) | +| `contractCalls` | - | `Vector` | [`*CALL`](./instruction-set#isa-section-call) | > The types tracked in these trace vectors are defined [here](./type-structs). -> The syntax `*CALL` is short for `CALL`/`STATICCALL`/`DELEGATECALL`. +> `*CALL` is short for `CALL`/`STATICCALL`/`DELEGATECALL`. > Aztec tree operations like membership checks, appends, or leaf updates are performed in-circuit by downstream circuits (public kernel and rollup circuits), _after_ AVM execution. The world state access trace is a list of requests made by the AVM for later circuits to perform. @@ -84,12 +85,13 @@ Each entry in the world state access trace is listed below along with its type a > The term "accrued substate" is borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper). -**Accrued substate** is accrued throughout a context's execution, but updates to it are strictly never relevant to subsequent instructions, contract calls, or transactions. An execution context is always initialized with empty accrued substate, and instructions can append to its vectors which are _append-only_. If a contract call's execution succeeds, its accrued substate is appended to the caller's. If a contract's execution reverts, its accrued substate is ignored. There is no accrued substate "trace" that includes entries from reverted contexts. +**Accrued substate** is accrued throughout a context's execution, but updates to it are strictly never relevant to subsequent instructions, contract calls, or transactions. An execution context is always initialized with empty accrued substate. Its vectors are append-only, and the instructions listed below append to these vectors. If a contract call's execution succeeds, its accrued substate is appended to the caller's. If a contract's execution reverts, its accrued substate is ignored. #### _AccruedSubstate_ -| Field | Type | Instructions | Description | -| --- | --- | --- | --- | -| `unencryptedLogs` | `Vector` | `ULOG` | | -| `sentL2ToL1Messages` | `Vector` | `SENDL1TOL2MSG` | | + +| Field | Type | Instructions | +| --- | --- | --- | +| `unencryptedLogs` | `Vector` | [`ULOG`](./instruction-set#isa-secction-ulog) | +| `sentL2ToL1Messages` | `Vector` | [`SENDL1TOL2MSG`](./instruction-set#isa-secction-sendl2tol1msg) | > The types tracked in these vectors are defined [here](./type-structs). diff --git a/yellow-paper/docs/public-vm/type-structs.md b/yellow-paper/docs/public-vm/type-structs.md index 948497c70c7..122821a487a 100644 --- a/yellow-paper/docs/public-vm/type-structs.md +++ b/yellow-paper/docs/public-vm/type-structs.md @@ -16,11 +16,24 @@ This section lists type definitions relevant to AVM State and Circuit I/O. | --- | --- | --- | | `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | | `portal` | `EthAddress` | | -| `msgKey` | `field` | | +| `leafIndex` | `field` | | +| `msgKey` | `field` | The message key which is also the tree leaf value. | +| `exists` | `field` | | | `message` | `[field; MAX_L1_TO_L2_MESSAGE_LENGTH]` | **Omitted from public inputs** | | `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. | -#### _TracedStorageAccess_ +#### _TracedStorageRead_ + +| Field | Type | Description | +| --- | --- | --- | +| `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls`| +| `slot` | `field` | | +| `exists` | `field` | Whether this slot has ever been previously written | +| `value` | `field` | | +| `counter` | `field` | | +| `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. The last `counter` at which this read/write should be considered to "exist" if this call or a parent reverted. | + +#### _TracedStorageWrite_ | Field | Type | Description | | --- | --- | --- | @@ -65,8 +78,8 @@ This section lists type definitions relevant to AVM State and Circuit I/O. | Field | Type | Description | | --- | --- | --- | | `callPointer` | `field` | Associates this item with a `TracedContractCall` entry in `worldStateAccessTrace.contractCalls` | -| `leaf` | `field` | | | `leafIndex` | `field` | | +| `leaf` | `field` | | | `exists` | `field` | | | `counter` | `field` | | | `endLifetime` | `field` | Equivalent to `endLifetime` of the containing contract call. | diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 8ccc09061b1..555549e2c2d 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -1,15 +1,15 @@ const {instructionSize} = require('./InstructionSize'); const TOPICS_IN_TABLE = [ - "Name", "Summary", "Bit-size", "Expression", + "Name", "Summary", "Expression", ]; const TOPICS_IN_SECTIONS = [ - "Name", "Summary", "Category", "Flags", "Args", "Expression", "Details", "Tag checks", "Tag updates", "Bit-size", + "Name", "Summary", "Category", "Flags", "Args", "Expression", "Details", "World State access tracing", "Additional AVM circuit checks", "Triggers downstream circuit operations", "Tag checks", "Tag updates", "Bit-size", ]; -const IN_TAG_DESCRIPTION = "The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with."; +const IN_TAG_DESCRIPTION = "The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with."; const IN_TAG_DESCRIPTION_NO_FIELD = IN_TAG_DESCRIPTION + " `field` type is NOT supported for this instruction."; -const DST_TAG_DESCRIPTION = "The [tag/size](./memory-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against."; +const DST_TAG_DESCRIPTION = "The [tag/size](./state-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against."; const INDIRECT_FLAG_DESCRIPTION = "Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`."; const INSTRUCTION_SET_RAW = [ @@ -273,7 +273,7 @@ const INSTRUCTION_SET_RAW = [ ], "Expression": "`M[dstOffset] = cast(M[aOffset])`", "Summary": "Type cast", - "Details": "Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./memory-model#cast-and-tag-conversions) for more details.", + "Details": "Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./state-model#cast-and-tag-conversions) for more details.", "Tag checks": "", "Tag updates": "`T[dstOffset] = dstTag`", }, @@ -681,7 +681,7 @@ context.machineState.pc = loc "Category": "Machine State - Memory", "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, - {"name": "inTag", "description": "The [type/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET."}, + {"name": "inTag", "description": "The [type/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET."}, ], "Args": [ {"name": "const", "description": "an N-bit constant value from the bytecode to store in memory (any type except `field`)", "mode": "immediate"}, @@ -729,23 +729,6 @@ context.machineState.pc = loc "Tag checks": "", "Tag updates": "`T[dstOffset] = M[condOffset] > 0 ? T[aOffset] : T[bOffset]`", }, - { - "id": "blockheaderbynum", - "Name": "`BLOCKHEADERBYNUM`", - "Category": "World State", - "Flags": [ - {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, - ], - "Args": [ - {"name": "blockNumOffset", "description": "memory offset of the block number input"}, - {"name": "dstOffset", "description": "memory offset specifying where to store operation's result's 0th word"}, - ], - "Expression": "`M[dstOffset:dstOffset+BLOCK_HEADER_LENGTH] = context.worldState.blockHeader[M[blockNumOffset]]`", - "Summary": "Get the block header as of the specified block number", - "Details": "", - "Tag checks": "", - "Tag updates": "`T[dstOffset:dstOffset+BLOCK_HEADER_LENGTh] = field`", - }, { "id": "sload", "Name": "`SLOAD`", @@ -757,9 +740,32 @@ context.machineState.pc = loc {"name": "slotOffset", "description": "memory offset of the storage slot to load from"}, {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, ], - "Expression": "`M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress, M[slotOffset]]`", - "Summary": "Load a word from storage", - "Details": "Load a word from this contract's persistent public storage into memory.", + "Expression": ` +M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] +`, + "Summary": "Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots.", + "Details": ` +// Expression is short-hand for +leafIndex = hash(context.environment.storageAddress, M[slotOffset]) +exists = context.worldState.publicStorage.has(leafIndex) // exists == previously-written +if exists: + value = context.worldState.publicStorage.get(leafIndex: leafIndex) +else: + value = 0 +M[dstOffset] = value +`, + "World State access tracing": ` +context.worldStateAccessTrace.publicStorageReads.append( + TracedStorageRead { + callPointer: context.environment.callPointer, + slot: M[slotOffset], + exists: exists, // defined above + value: value, // defined above + counter: clk, + } +) +`, + "Triggers downstream circuit operations": "Storage slot siloing (hash with contract address), public data tree membership check", "Tag checks": "", "Tag updates": "`T[dstOffset] = field`", }, @@ -774,62 +780,126 @@ context.machineState.pc = loc {"name": "srcOffset", "description": "memory offset of the word to store"}, {"name": "slotOffset", "description": "memory offset containing the storage slot to store to"}, ], - "Expression": "`context.worldState.publicStorage[context.environment.storageAddress, M[slotOffset]] = M[srcOffset]`", - "Summary": "Write a word to storage", - "Details": "Store a word from memory into this contract's persistent public storage.", + "Expression": ` +context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] = M[srcOffset] +`, + "Summary": "Write a word to this contract's persistent public storage", + "Details": ` +// Expression is short-hand for +context.worldState.publicStorage.set({ + leafIndex: hash(context.environment.storageAddress, M[slotOffset]), + leaf: M[srcOffset], +}) +`, + "World State access tracing": ` +context.worldStateAccessTrace.publicStorageWrites.append( + TracedStorageWrite { + callPointer: context.environment.callPointer, + slot: M[slotOffset], + value: M[srcOffset], + counter: clk, + } +) +`, + "Triggers downstream circuit operations": "Storage slot siloing (hash with contract address), public data tree update", "Tag checks": "", "Tag updates": "", }, { - "id": "readl1tol2msg", - "Name": "`READL1TOL2MSG`", - "Category": "World State - Messaging", + "id": "notehashexists", + "Name": "`NOTEHASHEXISTS`", + "Category": "World State - Notes & Nullifiers", "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], "Args": [ - {"name": "msgKeyOffset", "description": "memory offset of the message's key"}, - {"name": "dstOffset", "description": "memory offset to place the 0th word of the message content"}, - {"name": "msgSize", "description": "number of words in the message", "mode": "immediate", "type": "u32"}, + {"name": "leafOffset", "description": "memory offset of the leaf"}, + {"name": "leafIndexOffset", "description": "memory offset of the leaf index"}, + {"name": "existsOffset", "description": "memory offset specifying where to store operation's result (whether the archive leaf exists)"}, ], - "Expression": "`M[dstOffset:dstOffset+msgSize] = context.worldState.l1ToL2Messages(M[msgKeyOffset])`", - "Summary": "Reads an L1-to-L2 message", - "Details": "", + "Expression": ` +exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.storageAddress, M[leafOffset]), +}) +M[existsOffset] = exists +`, + "Summary": "Check whether a note hash exists in the note hash tree (as of the start of the current block)", + "World State access tracing": ` +context.worldStateAccessTrace.noteHashChecks.append( + TracedLeafCheck { + callPointer: context.environment.callPointer, + leafIndex: M[leafIndexOffset] + leaf: M[leafOffset], + exists: exists, // defined above + counter: clk, + } +) +`, + "Triggers downstream circuit operations": "Note hash siloing (hash with storage contract address), note hash tree membership check", "Tag checks": "", - "Tag updates": "`T[dstOffset:dstOffset+msgSize] = field`", + "Tag updates": "`T[dstOffset] = u8`", }, { - "id": "sendl2tol1msg", - "Name": "`SENDL2TOL1MSG`", - "Category": "World State - Messaging", + "id": "emitnotehash", + "Name": "`EMITNOTEHASH`", + "Category": "World State - Notes & Nullifiers", "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], "Args": [ - {"name": "msgOffset", "description": "memory offset of the message content"}, - {"name": "msgSize", "description": "number of words in the message", "mode": "immediate", "type": "u32"}, + {"name": "noteHashOffset", "description": "memory offset of the note hash"}, ], - "Expression": "`context.worldState.l2ToL1Messages.append(M[msgOffset:msgOffset+msgSize])`", - "Summary": "Send an L2-to-L1 message", - "Details": "", + "Expression": ` +context.worldState.noteHashes.append( + hash(context.environment.storageAddress, M[noteHashOffset]) +) +`, + "Summary": "Emit a new note hash to be inserted into the note hash tree", + "World State access tracing": ` +context.worldStateAccessTrace.newNoteHashes.append( + TracedNoteHash { + callPointer: context.environment.callPointer, + value: M[noteHashOffset], // unsiloed note hash + counter: clk, + } +) +`, + "Triggers downstream circuit operations": "Note hash siloing (hash with contract address), note hash tree insertion.", "Tag checks": "", "Tag updates": "", }, { - "id": "emitnotehash", - "Name": "`EMITNOTEHASH`", + "id": "nullifierexists", + "Name": "`NULLIFIEREXISTS`", "Category": "World State - Notes & Nullifiers", "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], "Args": [ - {"name": "noteHashOffset", "description": "memory offset of the note hash"}, + {"name": "nullifierOffset", "description": "memory offset of the unsiloed nullifier"}, + {"name": "existsOffset", "description": "memory offset specifying where to store operation's result (whether the nullifier exists)"}, ], - "Expression": "`context.worldState.newHashes.append(M[noteHashOffset])`", - "Summary": "Emit a new note hash to be inserted into the notes tree", - "Details": "", + "Expression": ` +exists = context.worldState.nullifiers.has( + hash(context.environment.storageAddress, M[nullifierOffset]) +) +M[existsOffset] = exists +`, + "Summary": "Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block)", + "World State access tracing": ` +context.worldStateAccessTrace.nullifierChecks.append( + TracedIndexedLeafCheck { + callPointer: context.environment.callPointer, + leaf: M[nullifierOffset], + exists: exists, // defined above + counter: clk, + } +) +`, + "Triggers downstream circuit operations": "Nullifier siloing (hash with storage contract address), nullifier tree membership check", "Tag checks": "", - "Tag updates": "", + "Tag updates": "`T[dstOffset] = u8`", }, { "id": "emitnullifier", @@ -841,12 +911,95 @@ context.machineState.pc = loc "Args": [ {"name": "nullifierOffset", "description": "memory offset of nullifier"}, ], - "Expression": "`context.worldState.nullifiers.append(M[nullifierOffset])`", + "Expression": ` +context.worldState.nullifiers.append( + hash(context.environment.storageAddress, M[nullifierOffset]) +) +`, "Summary": "Emit a new nullifier to be inserted into the nullifier tree", - "Details": "", + "World State access tracing": ` +context.worldStateAccessTrace.newNullifiers.append( + TracedNullifier { + callPointer: context.environment.callPointer, + value: M[nullifierOffset], // unsiloed nullifier + counter: clk, + } +) +`, + "Triggers downstream circuit operations": "Nullifier siloing (hash with contract address), nullifier tree non-membership-check and insertion.", "Tag checks": "", "Tag updates": "", }, + { + "id": "readl1tol2msg", + "Name": "`READL1TOL2MSG`", + "Category": "World State - Messaging", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + ], + "Args": [ + {"name": "msgKeyOffset", "description": "memory offset of the message's key"}, + {"name": "msgLeafIndex", "description": "memory offset of the message's leaf index in the L1-to-L2 message tree"}, + {"name": "existsOffset", "description": "memory offset specifying where to store operation's result (whether the message exists in the L1-to-L2 message tree)"}, + {"name": "dstOffset", "description": "memory offset to place the 0th word of the message content"}, + {"name": "msgSize", "description": "number of words in the message", "mode": "immediate", "type": "u32"}, + ], + "Expression": ` +exists = context.worldState.l1ToL2Messages.has({ + leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] +}) +M[existsOffset] = exists +if exists: + M[dstOffset:dstOffset+msgSize] = context.worldState.l1ToL2Messages.get({ + leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] + }) +`, + "Summary": "Check if a message exists in the L1-to-L2 message tree and reads it if so.", + "World State access tracing": ` +context.worldStateAccessTrace.l1ToL2MessagesReads.append( + ReadL1ToL2Message { + callPointer: context.environment.callPointer, + portal: context.environment.portal, + leafIndex: M[msgLeafIndex], + msgKey: M[msgKeyOffset], + exists: exists, // defined above + } +) +`, + "Additional AVM circuit checks": "`msgKey == sha256_to_field(msg)`", + "Triggers downstream circuit operations": "L1-to-L2 message tree membership check", + "Tag checks": "", + "Tag updates": "`T[dstOffset:dstOffset+msgSize] = field`", + }, + { + "id": "headermember", + "Name": "`HEADERMEMBER`", + "Category": "World State - Archive Tree & Headers", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + ], + "Args": [ + {"name": "blockIndexOffset", "description": "memory offset of the block index (same as archive tree leaf index) of the header to access"}, + {"name": "memberIndexOffset", "description": "memory offset of the index of the member to retrieve from the header of the specified block"}, + {"name": "dstOffset", "description": "memory offset specifying where to store operation's result (the retrieved header member)"}, + ], + "Expression": ` +M[dstOffset] = context.worldState.headers.get(M[blockIndexOffset])[M[memberIndexOffset]] +`, + "Summary": "Retrieve one member from a specified block's header. Revert if header does not yet exist. See [\"Archive\"](../state/archive) for more.", + "World State access tracing": ` +context.worldStateAccessTrace.archiveChecks.append( + TracedArchiveLeafCheck { + leafIndex: M[blockIndexOffset], // leafIndex == blockIndex + leaf: hash(context.worldState.headers.get(M[blockIndexOffset])), + } +) +`, + "Additional AVM circuit checks": "Hashes entire header to archive leaf for tracing. Aggregates header accesses and so that a header need only be hashed once.", + "Triggers downstream circuit operations": "Archive tree membership check", + "Tag checks": "", + "Tag updates": "`T[dstOffset] = field`", + }, { "id": "emitunencryptedlog", "Name": "`EMITUNENCRYPTEDLOG`", @@ -858,9 +1011,39 @@ context.machineState.pc = loc {"name": "logOffset", "description": "memory offset of the data to log"}, {"name": "logSize", "description": "number of words to log", "mode": "immediate", "type": "u32"}, ], - "Expression": "`context.accruedSubstate.unencryptedLogs.append(M[logOffset:logOffset+logSize])`", + "Expression": ` +context.accruedSubstate.unencryptedLogs.append( + UnencryptedLog { + address: context.environment.address, + log: M[logOffset:logOffset+logSize], + } +) +`, "Summary": "Emit an unencrypted log", - "Details": "", + "Tag checks": "", + "Tag updates": "", + }, + { + "id": "sendl2tol1msg", + "Name": "`SENDL2TOL1MSG`", + "Category": "Accrued Substate - Messaging", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + ], + "Args": [ + {"name": "msgOffset", "description": "memory offset of the message content"}, + {"name": "msgSize", "description": "number of words in the message", "mode": "immediate", "type": "u32"}, + ], + "Expression": ` +context.accruedSubstate.sentL2ToL1Messages.append( + SentL2ToL1Message { + address: context.environment.address, + portal: context.environment.portal, + message: M[msgOffset:msgOffset+msgSize] + } +) +`, + "Summary": "Send an L2-to-L1 message", "Tag checks": "", "Tag updates": "", }, From a10e6f6081f36b0ad5ed1396cc8f87f12e917204 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 12 Feb 2024 14:55:17 +0000 Subject: [PATCH 02/21] instruction set preface --- yellow-paper/docs/public-vm/instruction-set.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yellow-paper/docs/public-vm/instruction-set.mdx b/yellow-paper/docs/public-vm/instruction-set.mdx index 1693295d2de..3b751dd3984 100644 --- a/yellow-paper/docs/public-vm/instruction-set.mdx +++ b/yellow-paper/docs/public-vm/instruction-set.mdx @@ -7,6 +7,8 @@ The following notes are relevant to the table and sections below: - Any instruction whose description does not mention a program counter change simply increments it: `context.machineState.pc++` - All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./avm#gas-limits-and-tracking) - Any instruction can lead to an exceptional halt as specified in ["Exceptional halting"](./avm#exceptional-halting) +- The term `hash` used in expressions below represents a Poseidon hash operation. +- Type structures used in world state tracing operations are defined in ["Type Definitions"](./type-structs) import GeneratedInstructionSet from "./gen/_InstructionSet.mdx"; From ba0d6e58da59080ff561a61b62057c6301fdb639 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 12 Feb 2024 14:58:06 +0000 Subject: [PATCH 03/21] link fixes --- .../docs/public-vm/gen/_InstructionSet.mdx | 32 +++++++++---------- .../InstructionSet/InstructionSet.js | 8 ++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx index 102a56958b0..cd0ae5e96b9 100644 --- a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx +++ b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx @@ -460,7 +460,7 @@ Addition (a + b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -481,7 +481,7 @@ Subtraction (a - b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -502,7 +502,7 @@ Multiplication (a * b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -523,7 +523,7 @@ Unsigned division (a / b) - **Category**: Compute - Arithmetic - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -544,7 +544,7 @@ Equality check (a == b) - **Category**: Compute - Comparators - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -565,7 +565,7 @@ Less-than check (a < b) - **Category**: Compute - Comparators - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -586,7 +586,7 @@ Less-than-or-equals check (a <= b) - **Category**: Compute - Comparators - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -607,7 +607,7 @@ Bitwise AND (a & b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -628,7 +628,7 @@ Bitwise OR (a | b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -649,7 +649,7 @@ Bitwise XOR (a ^ b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -670,7 +670,7 @@ Bitwise NOT (inversion) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's input - **dstOffset**: memory offset specifying where to store operation's result @@ -690,7 +690,7 @@ Bitwise leftward shift (a << b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -711,7 +711,7 @@ Bitwise rightward shift (a >> b) - **Category**: Compute - Bitwise - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. + - **inTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for this instruction. - **Args**: - **aOffset**: memory offset of the operation's left input - **bOffset**: memory offset of the operation's right input @@ -732,12 +732,12 @@ Type cast - **Category**: Type Conversions - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **dstTag**: The [tag/size](./state-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against. + - **dstTag**: The [tag/size](./memory-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against. - **Args**: - **aOffset**: memory offset of word to cast - **dstOffset**: memory offset specifying where to store operation's result - **Expression**: `M[dstOffset] = cast(M[aOffset])` -- **Details**: Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./state-model#cast-and-tag-conversions) for more details. +- **Details**: Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./memory-model#cast-and-tag-conversions) for more details. - **Tag updates**: `T[dstOffset] = dstTag` - **Bit-size**: 96 @@ -1177,7 +1177,7 @@ Set a memory word from a constant in the bytecode - **Category**: Machine State - Memory - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - - **inTag**: The [type/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET. + - **inTag**: The [type/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET. - **Args**: - **const**: an N-bit constant value from the bytecode to store in memory (any type except `field`) - **dstOffset**: memory offset specifying where to store the constant diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 555549e2c2d..266ceaaf28d 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -7,9 +7,9 @@ const TOPICS_IN_SECTIONS = [ "Name", "Summary", "Category", "Flags", "Args", "Expression", "Details", "World State access tracing", "Additional AVM circuit checks", "Triggers downstream circuit operations", "Tag checks", "Tag updates", "Bit-size", ]; -const IN_TAG_DESCRIPTION = "The [tag/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with."; +const IN_TAG_DESCRIPTION = "The [tag/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with."; const IN_TAG_DESCRIPTION_NO_FIELD = IN_TAG_DESCRIPTION + " `field` type is NOT supported for this instruction."; -const DST_TAG_DESCRIPTION = "The [tag/size](./state-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against."; +const DST_TAG_DESCRIPTION = "The [tag/size](./memory-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against."; const INDIRECT_FLAG_DESCRIPTION = "Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`."; const INSTRUCTION_SET_RAW = [ @@ -273,7 +273,7 @@ const INSTRUCTION_SET_RAW = [ ], "Expression": "`M[dstOffset] = cast(M[aOffset])`", "Summary": "Type cast", - "Details": "Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./state-model#cast-and-tag-conversions) for more details.", + "Details": "Cast a word in memory based on the `dstTag` specified in the bytecode. Truncates (`M[dstOffset] = M[aOffset] mod 2^dstsize`) when casting to a smaller type, left-zero-pads when casting to a larger type. See [here](./memory-model#cast-and-tag-conversions) for more details.", "Tag checks": "", "Tag updates": "`T[dstOffset] = dstTag`", }, @@ -681,7 +681,7 @@ context.machineState.pc = loc "Category": "Machine State - Memory", "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, - {"name": "inTag", "description": "The [type/size](./state-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET."}, + {"name": "inTag", "description": "The [type/size](./memory-model#tags-and-tagged-memory) to check inputs against and tag the destination with. `field` type is NOT supported for SET."}, ], "Args": [ {"name": "const", "description": "an N-bit constant value from the bytecode to store in memory (any type except `field`)", "mode": "immediate"}, From e735d2e641138a09915d4640e8980cce67d2083b Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 12 Feb 2024 15:14:27 +0000 Subject: [PATCH 04/21] new opcodes in ts/rs --- avm-transpiler/src/opcodes.rs | 26 ++++++----- .../serialization/bytecode_serialization.ts | 44 ++++++++++--------- .../instruction_serialization.ts | 16 ++++--- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs index cc3c828d2ec..6948aca6239 100644 --- a/avm-transpiler/src/opcodes.rs +++ b/avm-transpiler/src/opcodes.rs @@ -60,16 +60,18 @@ pub enum AvmOpcode { CMOV, // World State - BLOCKHEADERBYNUMBER, - SLOAD, // Public Storage - SSTORE, // Public Storage - READL1TOL2MSG, // Messages - SENDL2TOL1MSG, // Messages - EMITNOTEHASH, // Notes & Nullifiers - EMITNULLIFIER, // Notes & Nullifiers + SLOAD, // Public Storage + SSTORE, // Public Storage + NOTEHASHEXISTS, // Notes & Nullifiers + EMITNOTEHASH, // Notes & Nullifiers + NULLIFIEREXISTS, // Notes & Nullifiers + EMITNULLIFIER, // Notes & Nullifiers + READL1TOL2MSG, // Messages + HEADERMEMBER, // Archive tree & Headers // Accrued Substate EMITUNENCRYPTEDLOG, + SENDL2TOL1MSG, // Control Flow - Contract Calls CALL, @@ -143,16 +145,18 @@ impl AvmOpcode { AvmOpcode::CMOV => "CMOV", // World State - AvmOpcode::BLOCKHEADERBYNUMBER => "BLOCKHEADERBYNUMBER", AvmOpcode::SLOAD => "SLOAD", // Public Storage AvmOpcode::SSTORE => "SSTORE", // Public Storage + AvmOpcode::NOTEHASHEXISTS => "NOTEHASHEXISTS", // Notes & Nullifiers + AvmOpcode::EMITNOTEHASH => "EMITNOTEHASH", // Notes & Nullifiers + AvmOpcode::NULLIFIEREXISTS => "NULLIFIEREXISTS", // Notes & Nullifiers + AvmOpcode::EMITNULLIFIER => "EMITNULLIFIER", // Notes & Nullifiers AvmOpcode::READL1TOL2MSG => "READL1TOL2MSG", // Messages - AvmOpcode::SENDL2TOL1MSG => "SENDL2TOL1MSG", // Messages - AvmOpcode::EMITNOTEHASH => "EMITNOTEHASH", // Notes & Nullifiers - AvmOpcode::EMITNULLIFIER => "EMITNULLIFIER", // Notes & Nullifiers + AvmOpcode::HEADERMEMBER => "HEADERMEMBER", // Archive tree & Headers // Accrued Substate AvmOpcode::EMITUNENCRYPTEDLOG => "EMITUNENCRYPTEDLOG", + AvmOpcode::SENDL2TOL1MSG => "SENDL2TOL1MSG", // Control Flow - Contract Calls AvmOpcode::CALL => "CALL", diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts index 1033cb5f785..756d44e906b 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts @@ -80,25 +80,25 @@ const INSTRUCTION_SET = () => [FeePerL1Gas.opcode, FeePerL1Gas], [FeePerL2Gas.opcode, FeePerL2Gas], [FeePerDAGas.opcode, FeePerDAGas], - // [Contractcalldepth.opcode, Contractcalldepth], + //[Contractcalldepth.opcode, Contractcalldepth], // Execution Environment - Globals [ChainId.opcode, ChainId], [Version.opcode, Version], [BlockNumber.opcode, BlockNumber], [Timestamp.opcode, Timestamp], - // [Coinbase.opcode, Coinbase], - // [Blockl1gaslimit.opcode, Blockl1gaslimit], - // [Blockl2gaslimit.opcode, Blockl2gaslimit], - // [Blockdagaslimit.opcode, Blockdagaslimit], - // // Execution Environment - Calldata + //[Coinbase.opcode, Coinbase], + //[Blockl1gaslimit.opcode, Blockl1gaslimit], + //[Blockl2gaslimit.opcode, Blockl2gaslimit], + //[Blockdagaslimit.opcode, Blockdagaslimit], + // Execution Environment - Calldata [CalldataCopy.opcode, CalldataCopy], - // //// Machine State - // // Machine State - Gas - // //[L1gasleft.opcode, L1gasleft], - // //[L2gasleft.opcode, L2gasleft], - // //[Dagasleft.opcode, Dagasleft], - // //// Machine State - Internal Control Flow + // Machine State + // Machine State - Gas + //[L1gasleft.opcode, L1gasleft], + //[L2gasleft.opcode, L2gasleft], + //[Dagasleft.opcode, Dagasleft], + // Machine State - Internal Control Flow [Jump.opcode, Jump], [JumpI.opcode, JumpI], [InternalCall.opcode, InternalCall], @@ -107,27 +107,29 @@ const INSTRUCTION_SET = () => [Mov.opcode, Mov], [CMov.opcode, CMov], - // //// World State - // //[Blockheaderbynumber.opcode, Blockheaderbynumber], + // World State [SLoad.opcode, SLoad], // Public Storage [SStore.opcode, SStore], // Public Storage - // //[Readl1tol2msg.opcode, Readl1tol2msg], // Messages - [SendL2ToL1Message.opcode, SendL2ToL1Message], // Messages + //[NoteHashExists.opcode, NoteHashExists], // Notes & Nullifiers [EmitNoteHash.opcode, EmitNoteHash], // Notes & Nullifiers + //[NullifierExists.opcode, NullifierExists], // Notes & Nullifiers [EmitNullifier.opcode, EmitNullifier], // Notes & Nullifiers + //[Readl1tol2msg.opcode, Readl1tol2msg], // Messages + //[HeaderMember.opcode, HeaderMember], // Header - // //// Accrued Substate + // Accrued Substate [EmitUnencryptedLog.opcode, EmitUnencryptedLog], + [SendL2ToL1Message.opcode, SendL2ToL1Message], - // //// Control Flow - Contract Calls + // Control Flow - Contract Calls [Call.opcode, Call], [StaticCall.opcode, StaticCall], [Return.opcode, Return], [Revert.opcode, Revert], - // //// Gadgets - // //[Keccak.opcode, Keccak], - // //[Poseidon.opcode, Poseidon], + // Gadgets + //[Keccak.opcode, Keccak], + //[Poseidon.opcode, Poseidon], ]); interface Serializable { diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts index 38acdf9bfdd..565fda5a832 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts @@ -49,14 +49,16 @@ export enum Opcode { SET, MOV, CMOV, - BLOCKHEADERBYNUMBER, - SLOAD, // Public Storage - SSTORE, // Public Storage - READL1TOL2MSG, // Messages - SENDL2TOL1MSG, // Messages - EMITNOTEHASH, // Notes & Nullifiers - EMITNULLIFIER, // Notes & Nullifiers + SLOAD, + SSTORE, + NOTEHASHEXISTS, + EMITNOTEHASH, + NULLIFIEREXISTS, + EMITNULLIFIER, + READL1TOL2MSG, + HEADERMEMBER, EMITUNENCRYPTEDLOG, + SENDL2TOL1MSG, CALL, STATICCALL, RETURN, From bd95cc4601adcd54c257c9e4fee9f0530f26f5d0 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 12 Feb 2024 15:18:42 +0000 Subject: [PATCH 05/21] new opcodes to cpp --- .../barretenberg/vm/avm_trace/AvmMini_opcode.cpp | 16 ++++++++++------ .../barretenberg/vm/avm_trace/AvmMini_opcode.hpp | 16 +++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp index ec3d7568f09..da7ccbcc249 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp @@ -63,16 +63,18 @@ const std::unordered_map Bytecode::OPERANDS_NUM = { //{ OpCode::CMOV, }, //// World State - //{ OpCode::BLOCKHEADERBYNUMBER, }, //{ OpCode::SLOAD, }, // Public Storage //{ OpCode::SSTORE, }, // Public Storage - //{ OpCode::READL1TOL2MSG, }, // Messages - //{ OpCode::SENDL2TOL1MSG, }, // Messages + //{ OpCode::NOTEHASHEXISTS, }, // Notes & Nullifiers //{ OpCode::EMITNOTEHASH, }, // Notes & Nullifiers + //{ OpCode::NULLIFIEREXISTS, }, // Notes & Nullifiers //{ OpCode::EMITNULLIFIER, }, // Notes & Nullifiers + //{ OpCode::READL1TOL2MSG, }, // Messages + //{ OpCode::HEADERMEMBER, }, //// Accrued Substate //{ OpCode::EMITUNENCRYPTEDLOG, }, + //{ OpCode::SENDL2TOL1MSG, }, //// Control Flow - Contract Calls //{ OpCode::CALL, }, @@ -132,14 +134,16 @@ bool Bytecode::has_in_tag(OpCode const op_code) case OpCode::INTERNALRETURN: case OpCode::MOV: case OpCode::CMOV: - case OpCode::BLOCKHEADERBYNUMBER: case OpCode::SLOAD: case OpCode::SSTORE: - case OpCode::READL1TOL2MSG: - case OpCode::SENDL2TOL1MSG: + case OpCode::NOTEHASHEXISTS: case OpCode::EMITNOTEHASH: + case OpCode::NULLIFIEREXISTS: case OpCode::EMITNULLIFIER: + case OpCode::READL1TOL2MSG: + case OpCode::HEADERMEMBER: case OpCode::EMITUNENCRYPTEDLOG: + case OpCode::SENDL2TOL1MSG: case OpCode::CALL: case OpCode::STATICCALL: case OpCode::RETURN: diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp index bdb03bde7b0..22659e54a1b 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp @@ -73,16 +73,18 @@ enum class OpCode : uint8_t { CMOV, // World State - BLOCKHEADERBYNUMBER, - SLOAD, // Public Storage - SSTORE, // Public Storage - READL1TOL2MSG, // Messages - SENDL2TOL1MSG, // Messages - EMITNOTEHASH, // Notes & Nullifiers - EMITNULLIFIER, // Notes & Nullifiers + SLOAD, // Public Storage + SSTORE, // Public Storage + NOTEHASHEXISTS, // Notes & Nullifiers + EMITNOTEHASH, // Notes & Nullifiers + NULLIFIEREXISTS, // Notes & Nullifiers + EMITNULLIFIER, // Notes & Nullifiers + READL1TOL2MSG, // Messages + HEADERMEMBER, // Archive tree & Headers // Accrued Substate EMITUNENCRYPTEDLOG, + SENDL2TOL1MSG, // Messages // Control Flow - Contract Calls CALL, From 64c6e8383b902d27e02d3dbb05cfca7de951366c Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 12 Feb 2024 18:17:57 +0000 Subject: [PATCH 06/21] update exceptional halting conditions in high-level avm spec --- yellow-paper/docs/public-vm/avm.md | 80 ++++++++++++++++++------------ 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 57d641614fd..6227fef9a59 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -87,10 +87,10 @@ A context's **execution environment** remains constant throughout a contract cal Finally, when a contract call halts, it sets the context's **contract call results** to communicate results to the caller. #### _ContractCallResults_ -| Field | Type | Description | -| --- | --- | --- | -| `reverted` | `boolean` | | -| `output` | `[field; ]` | | +| Field | Type | Description | +| --- | --- | --- | +| reverted | `boolean` | | +| output | `[field; ]` | | ## Execution @@ -210,9 +210,9 @@ The AVM's exceptional halting conditions area listed below: 1. **World state modification attempt during a static call** ``` assert !environment.isStaticCall - OR environment.bytecode[machineState.pc].opcode not in WS_MODIFYING_OPS + OR environment.bytecode[machineState.pc].opcode not in WS_AS_MODIFYING_OPS ``` - > Definition: `WS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state. + > Definition: `WS_AS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state or accrued substate. 1. **Maximum contract call depth (1024) exceeded** ``` assert environment.contractCallDepth <= 1024 @@ -231,35 +231,57 @@ The AVM's exceptional halting conditions area listed below: assert environment.bytecode[machineState.pc].opcode != INTERNALCALL OR environment.contractCallDepth < 1024 ``` -1. **Maximum world state accesses (1024-per-type) exceeded** +1. **Maximum world state accesses (1024-per-category) exceeded** ``` - assert publicStorageAccesses.length <= 1024 - AND l1ToL2MessagesReads.length <= 1024 - AND newL2ToL1Messages.length <= 1024 - AND newNoteHashes.length <= 1024 - AND newNullifiers.length <= 1024 + assert worldStateAccessTrace.publicStorageReads.length <= 1024 + AND worldStateAccessTrace.publicStorageWrites.length <= 1024 + AND worldStateAccessTrace.noteHashChecks.length <= 1024 + AND worldStateAccessTrace.newNoteHashes.length <= 1024 + AND worldStateAccessTrace.nullifierChecks.length <= 1024 + AND worldStateAccessTrace.newNullifiers.length <= 1024 + AND worldStateAccessTrace.l1ToL2MessageReads.length <= 1024 + AND worldStateAccessTrace.archiveChecks.length <= 1024 // Storage - assert environment.bytecode[machineState.pc].opcode not in {SLOAD, SSTORE} - OR publicStorageAccesses.length < 1024 - - // L1 to L2 messages - assert environment.bytecode[machineState.pc].opcode != GETL1TOL2MSG - OR l1ToL2MessagesReads.length < 1024 - - // L2 to L1 messages - assert environment.bytecode[machineState.pc].opcode != SENDL2TOL1MSG - OR newL2ToL1Messages.length < 1024 + assert environment.bytecode[machineState.pc].opcode != SLOAD + OR worldStateAccessTrace.publicStorageReads.length < 1024 + assert environment.bytecode[machineState.pc].opcode != SSTORE + OR worldStateAccessTrace.publicStorageWrites.length < 1024 // Note hashes + assert environment.bytecode[machineState.pc].opcode != NOTEHASHEXISTS + OR noteHashChecks.length < 1024 assert environment.bytecode[machineState.pc].opcode != EMITNOTEHASH OR newNoteHashes.length < 1024 // Nullifiers + assert environment.bytecode[machineState.pc].opcode != NULLIFIEREXISTS + OR nullifierChecks.length < 1024 assert environment.bytecode[machineState.pc].opcode != EMITNULLIFIER OR newNullifiers.length < 1024 + + // Read L1 to L2 messages + assert environment.bytecode[machineState.pc].opcode != READL1TOL2MSG + OR worldStateAccessTrace.l1ToL2MessagesReads.length < 1024 + + // Archive tree & Headers + assert environment.bytecode[machineState.pc].opcode != HEADERMEMBER + OR archiveChecks.length < 1024 + ``` +1. **Maximum accrued substate entries (per-category) exceeded** ``` - > Definition: `WS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state. + assert accruedSubstate.unencryptedLogs.length <= MAX_UNENCRYPTED_LOGS + AND accruedSubstate.sentL2ToL1Messages.length <= MAX_SENT_L2_TO_L1_MESSAGES + + // Unencrypted logs + assert environment.bytecode[machineState.pc].opcode != ULOG + OR unencryptedLogs.length < MAX_UNENCRYPTED_LOGS + + // Sent L2 to L1 messages + assert environment.bytecode[machineState.pc].opcode != SENDL2TOL1MSG + OR sentL2ToL1Messages.length < MAX_SENT_L2_TO_L1_MESSAGES + ``` + > Note that ideally the AVM should limit the _total_ accrued substate entries per-category instead of the entries per-call. ## Initial contract calls @@ -273,8 +295,8 @@ context = AvmContext { environment = INITIAL_EXECUTION_ENVIRONMENT, machineState = INITIAL_MACHINE_STATE, worldState = , - worldStateAccessTrace = { [], [], ... [] } // all trace vectors empty, - accruedSubstate = INITIAL_ACCRUED_SUBSTATE, + worldStateAccessTrace = { [], [], ... [] }, // all trace vectors empty, + accruedSubstate = { [], ... [], }, // all substate vectors empty results = INITIAL_CONTRACT_CALL_RESULTS, } ``` @@ -310,10 +332,6 @@ INITIAL_MACHINE_STATE = MachineState { memory = [0, ..., 0], // all 2^32 entries are initialized to zero } -INITIAL_ACCRUED_SUBSTATE = AccruedSubstate { - unencryptedLogs = [], // initialized as empty -} - INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { reverted = false, output = [], // initialized as empty @@ -334,7 +352,7 @@ nestedContext = AvmContext { machineState: nestedMachineState, // defined below worldState: callingContext.worldState, worldStateAccessTrace: callingContext.worldStateAccessTrace, - accruedSubstate: INITIAL_ACCRUED_SUBSTATE, + accruedSubstate = { [], ... [], }, // all substate vectors empty results: INITIAL_CONTRACT_CALL_RESULTS, } ``` @@ -425,7 +443,7 @@ if !nestedContext.results.reverted AND instr.opcode != STATICCALL_OP: Regardless of whether a nested context has reverted, its [world state access trace](./state#world-state-access-trace) updates are absorbed into the calling context along with a new `contractCalls` entry. ``` context.worldStateAccessTrace = nestedContext.worldStateAccessTrace -context.worldStateAccessTrace.contractCalls.append({nestedContext.address, clk}) +context.worldStateAccessTrace.contractCalls.append({nestedContext.address, nestedContext.storageAddress, clk}) ``` > Reminder: a nested call cannot make updates to the world state or accrued substate if it is a [`STATICCALL`](./instruction-set/#isa-section-staticcall). From e3115c5e6368d1483eabab918911ad8df313b5e9 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 21:34:59 +0000 Subject: [PATCH 07/21] docs(yellowpaper): overhaul of AVM sections --- yellow-paper/docs/public-vm/avm.md | 93 ++++--- .../docs/public-vm/gen/_InstructionSet.mdx | 229 ++++++++++++++---- .../docs/public-vm/instruction-set.mdx | 1 + yellow-paper/docs/public-vm/nested-calls.md | 150 ++++++++++++ yellow-paper/docs/public-vm/state.md | 45 ++-- yellow-paper/docs/public-vm/type-structs.md | 4 +- yellow-paper/sidebars.js | 6 +- .../InstructionSet/InstructionSet.js | 154 +++++++++--- 8 files changed, 539 insertions(+), 143 deletions(-) create mode 100644 yellow-paper/docs/public-vm/nested-calls.md diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 6227fef9a59..a24d5ec44ff 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -67,24 +67,24 @@ A context's **execution environment** remains constant throughout a contract cal #### _ExecutionEnvironment_ | Field | Type | Description | | --- | --- | --- | +| address | `AztecAddress` | | +| storageAddress | `AztecAddress` | | | origin | `AztecAddress` | | +| sender | `AztecAddress` | | +| portal | `EthAddress` | | | feePerL1Gas | `field` | | | feePerL2Gas | `field` | | | feePerDaGas | `field` | | +| contractCallDepth | `field` | Depth of the current call (how many nested calls deep is it). | +| contractCallPointer | `field` | Uniquely identifies each contract call processed by an AVM session. An initial call is assigned pointer value of 1 (expanded on in the AVM circuit section's ["Call Pointer"](./avm-circuit#call-pointer) subsection). | | globals | `PublicGlobalVariables` | | -| address | `AztecAddress` | | -| storageAddress | `AztecAddress` | | -| sender | `AztecAddress` | | -| portal | `AztecAddress` | | -| contractCallDepth | `field` | | | isStaticCall | `boolean` | | | isDelegateCall | `boolean` | | | calldata | `[field; ]` | | -| bytecode | `[field; ]` | | ### Contract Call Results -Finally, when a contract call halts, it sets the context's **contract call results** to communicate results to the caller. +When a contract call halts, it sets the context's **contract call results** to communicate results to the caller. #### _ContractCallResults_ | Field | Type | Description | @@ -100,7 +100,7 @@ Once an execution context has been initialized for a contract call, the [machine The program counter (`machineState.pc`) determines which instruction the AVM executes next (`instr = environment.bytecode[pc]`). Each instruction's execution updates the program counter in some way, which allows the AVM to progress to the next instruction at each step. -Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./instruction-set#isa-section-jump), [`JUMPI`](./instruction-set#isa-section-jumpi), `INTERNALCALL`) modify the program counter based on arguments. +Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./instruction-set#isa-section-jump), [`JUMPI`](./instruction-set#isa-section-jumpi), [`INTERNALCALL`](./instruction-set#isa-section-internalcall)) modify the program counter based on arguments. The `INTERNALCALL` instruction pushes `machineState.pc+1` to `machineState.internalCallStack` and then updates `pc` to the instruction's destination argument (`instr.args.loc`). The `INTERNALRETURN` instruction pops a destination from `machineState.internalCallStack` and assigns the result to `pc`. @@ -295,7 +295,7 @@ context = AvmContext { environment = INITIAL_EXECUTION_ENVIRONMENT, machineState = INITIAL_MACHINE_STATE, worldState = , - worldStateAccessTrace = { [], [], ... [] }, // all trace vectors empty, + worldStateAccessTrace = INITIAL_WORLD_STATE_ACCESS_TRACE, accruedSubstate = { [], ... [], }, // all substate vectors empty results = INITIAL_CONTRACT_CALL_RESULTS, } @@ -307,31 +307,46 @@ Given a [`PublicCallRequest`](../transactions/tx-object#public-call-request) and ``` INITIAL_EXECUTION_ENVIRONMENT = ExecutionEnvironment { - address = PublicCallRequest.contractAddress, - storageAddress = PublicCallRequest.CallContext.storageContractAddress, - origin = TxRequest.origin, - sender = PublicCallRequest.CallContext.msgSender, - portal = PublicCallRequest.CallContext.portalContractAddress, - feePerL1Gas = TxRequest.feePerL1Gas, - feePerL2Gas = TxRequest.feePerL2Gas, - feePerDaGas = TxRequest.feePerDaGas, - contractCallDepth = 0, - globals = - isStaticCall = PublicCallRequest.CallContext.isStaticCall, - isDelegateCall = PublicCallRequest.CallContext.isDelegateCall, - calldata = PublicCallRequest.args, - bytecode = worldState.contracts[PublicCallRequest.contractAddress], + address: PublicCallRequest.contractAddress, + storageAddress: PublicCallRequest.CallContext.storageContractAddress, + origin: TxRequest.origin, + sender: PublicCallRequest.CallContext.msgSender, + portal: PublicCallRequest.CallContext.portalContractAddress, + feePerL1Gas: TxRequest.feePerL1Gas, + feePerL2Gas: TxRequest.feePerL2Gas, + feePerDaGas: TxRequest.feePerDaGas, + contractCallDepth: 0, + contractCallPointer: 1, + globals: + isStaticCall: PublicCallRequest.CallContext.isStaticCall, + isDelegateCall: PublicCallRequest.CallContext.isDelegateCall, + calldata: PublicCallRequest.args, + bytecode: worldState.contracts[PublicCallRequest.contractAddress], } INITIAL_MACHINE_STATE = MachineState { - l1GasLeft = TxRequest.l1GasLimit, - l2GasLeft = TxRequest.l2GasLimit, - daGasLeft = TxRequest.daGasLimit, - pc = 0, - internalCallStack = [], // initialized as empty - memory = [0, ..., 0], // all 2^32 entries are initialized to zero + l1GasLeft: TxRequest.l1GasLimit, + l2GasLeft: TxRequest.l2GasLimit, + daGasLeft: TxRequest.daGasLimit, + pc: 0, + internalCallStack: [], // initialized as empty + memory: [0, ..., 0], // all 2^32 entries are initialized to zero } +INITIAL_WORLD_STATE_ACCESS_TRACE = WorldStateAccessTrace { + accessCounter: 1, + contractCalls: [ // initial contract call is traced + TracedContractCall { + callPointer: nestedContext.environment.callPointer, + address: nestedContext.address, + storageAddress: nestedContext.storageAddress, + counter: 0, + endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts + } + ], + [], ... [], // remaining entries are empty +}, + INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { reverted = false, output = [], // initialized as empty @@ -381,6 +396,7 @@ nestedExecutionEnvironment = ExecutionEnvironment { feePerL2Gas: callingContext.feePerL2Gas, feePerDaGas: callingContext.feePerDaGas, contractCallDepth: callingContext.contractCallDepth + 1, + contractCallPointer: callingContext.worldStateAccessTrace.contractCalls.length + 1, globals: callingContext.globals, isStaticCall: isStaticCall, isDelegateCall: isDelegateCall, @@ -440,10 +456,25 @@ if !nestedContext.results.reverted AND instr.opcode != STATICCALL_OP: context.accruedSubstate.append(nestedContext.accruedSubstate) ``` -Regardless of whether a nested context has reverted, its [world state access trace](./state#world-state-access-trace) updates are absorbed into the calling context along with a new `contractCalls` entry. +Before the nested call's world state access trace is absorbed into the calling context, if the nested context reverted, all accesses made during the nested call (or any further nested calls) must be flagged with an **end-lifetime**, signifying that the access is only relevant up until that point. Thus, the `endLifetime` for all accesses made during the nested call (or any deeper nested calls) is updated according to the nested call's final `accessCounter`. +``` +if nestedContext.results.reverted: + // process all traces (this is shorthand) + for trace in nestedContext.worldStateAccessTrace: + for access in trace: + if access.callPointer >= nestedContext.environment.callPointer: + // don't override end-lifetime already set by a deeper nested call + if access.endLifetime == 0: + access.endLifetime = nestedContext.worldStateAccessTrace.accessCounter +``` + +> Note that `endLifetime` of 0 signifies "no end". + +> Note this also updates the nested call's entry in the `contractCalls` trace. + +Regardless of whether a nested context has reverted, its [world state access trace](./state#world-state-access-trace) updates are absorbed into the calling context. ``` context.worldStateAccessTrace = nestedContext.worldStateAccessTrace -context.worldStateAccessTrace.contractCalls.append({nestedContext.address, nestedContext.storageAddress, clk}) ``` > Reminder: a nested call cannot make updates to the world state or accrued substate if it is a [`STATICCALL`](./instruction-set/#isa-section-staticcall). diff --git a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx index cd0ae5e96b9..a18b63db11b 100644 --- a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx +++ b/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx @@ -312,14 +312,14 @@ context.machineState.pc = loc`} 0x2a [`SLOAD`](#isa-section-sload) Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots. -{`M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]]`} +{`M[dstOffset] = S[M[slotOffset]]`}
0x2b [`SSTORE`](#isa-section-sstore) Write a word to this contract's persistent public storage -{`context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] = M[srcOffset]`} +{`S[M[slotOffset]] = M[srcOffset]`}
0x2d [`EMITNOTEHASH`](#isa-section-emitnotehash)0x2d [`NOTEHASHEXISTS`](#isa-section-notehashexists)Check whether a note hash exists in the note hash tree (as of the start of the current block) +{`exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.storageAddress, M[leafOffset]), +}) +M[existsOffset] = exists`} +
0x2e [`EMITNOTEHASH`](#isa-section-emitnotehash) Emit a new note hash to be inserted into the note hash tree {`context.worldState.noteHashes.append( @@ -343,7 +354,7 @@ M[existsOffset] = exists`}
0x2e [`NULLIFIEREXISTS`](#isa-section-nullifierexists)0x2f [`NULLIFIEREXISTS`](#isa-section-nullifierexists) Check whether a nullifier exists in the nullifier tree (including nullifiers from earlier in the current transaction or from earlier in the current block) {`exists = context.worldState.nullifiers.has( @@ -353,7 +364,7 @@ M[existsOffset] = exists`}
0x2f [`EMITNULLIFIER`](#isa-section-emitnullifier)0x30 [`EMITNULLIFIER`](#isa-section-emitnullifier) Emit a new nullifier to be inserted into the nullifier tree {`context.worldState.nullifiers.append( @@ -362,8 +373,8 @@ M[existsOffset] = exists`}
0x30 [`READL1TOL2MSG`](#isa-section-readl1tol2msg)Check if a message exists in the L1-to-L2 message tree and reads it if so.0x31 [`READL1TOL2MSG`](#isa-section-readl1tol2msg)Check if a message exists in the L1-to-L2 message tree and reads it if so {`exists = context.worldState.l1ToL2Messages.has({ leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] @@ -376,14 +387,20 @@ if exists:
0x31 [`HEADERMEMBER`](#isa-section-headermember)Retrieve one member from a specified block's header. Revert if header does not yet exist. See ["Archive"](../state/archive) for more.0x32 [`HEADERMEMBER`](#isa-section-headermember)Check if a header exists in the [archive tree](../state/archive) and retrieve the specified member if so -{`M[dstOffset] = context.worldState.headers.get(M[blockIndexOffset])[M[memberIndexOffset]]`} +{`exists = context.worldState.header.has({ + leafIndex: M[blockIndexOffset], leaf: M[msgKeyOffset] +}) +M[existsOffset] = exists +if exists: + header = context.worldState.headers.get(M[blockIndexOffset]) + M[dstOffset] = header[M[memberIndexOffset]] // member`}
0x32 [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog)0x33 [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog) Emit an unencrypted log {`context.accruedSubstate.unencryptedLogs.append( @@ -395,7 +412,7 @@ if exists:
0x33 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg)0x34 [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) Send an L2-to-L1 message {`context.accruedSubstate.sentL2ToL1Messages.append( @@ -408,7 +425,7 @@ if exists:
0x34 [`CALL`](#isa-section-call)0x35 [`CALL`](#isa-section-call) Call into another contract {`M[successOffset] = call( @@ -419,7 +436,7 @@ if exists:
0x35 [`STATICCALL`](#isa-section-staticcall)0x36 [`STATICCALL`](#isa-section-staticcall) Call into another contract, disallowing World State and Accrued Substate modifications {`M[successOffset] = staticcall( @@ -430,7 +447,18 @@ if exists:
0x36 [`RETURN`](#isa-section-return)0x37 [`DELEGATECALL`](#isa-section-delegatecall)Call into another contract, but keep the caller's `sender` and `storageAddress` +{`M[successOffset] = delegatecall( + M[gasOffset], M[gasOffset+1], M[gasOffset+2], + M[addrOffset], + M[argsOffset], M[argsSize], + M[retOffset], M[retSize])`} +
0x38 [`RETURN`](#isa-section-return) Halt execution within this context (without revert), optionally returning some data {`context.contractCallResults.output = M[retOffset:retOffset+retSize] @@ -438,7 +466,7 @@ halt`}
0x37 [`REVERT`](#isa-section-revert)0x39 [`REVERT`](#isa-section-revert) Halt execution within this context as `reverted`, optionally returning some data {`context.contractCallResults.output = M[retOffset:retOffset+retSize] @@ -1241,11 +1269,11 @@ Load a word from this contract's persistent public storage. Zero is loaded for u - **dstOffset**: memory offset specifying where to store operation's result - **Expression**: -{`M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]]`} +{`M[dstOffset] = S[M[slotOffset]]`} - **Details**: -{`// Expression is short-hand for +{`// Expression is shorthand for leafIndex = hash(context.environment.storageAddress, M[slotOffset]) exists = context.worldState.publicStorage.has(leafIndex) // exists == previously-written if exists: @@ -1262,7 +1290,7 @@ M[dstOffset] = value`} slot: M[slotOffset], exists: exists, // defined above value: value, // defined above - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } )`} @@ -1286,11 +1314,11 @@ Write a word to this contract's persistent public storage - **slotOffset**: memory offset containing the storage slot to store to - **Expression**: -{`context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] = M[srcOffset]`} +{`S[M[slotOffset]] = M[srcOffset]`} - **Details**: -{`// Expression is short-hand for +{`// Expression is shorthand for context.worldState.publicStorage.set({ leafIndex: hash(context.environment.storageAddress, M[slotOffset]), leaf: M[srcOffset], @@ -1303,7 +1331,7 @@ context.worldState.publicStorage.set({ callPointer: context.environment.callPointer, slot: M[slotOffset], value: M[srcOffset], - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } )`} @@ -1341,12 +1369,49 @@ M[existsOffset] = exists`} leafIndex: M[leafIndexOffset] leaf: M[leafOffset], exists: exists, // defined above - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, + } +)`} + +- **Triggers downstream circuit operations**: Storage slot siloing (hash with contract address), public data tree update +- **Tag updates**: T[existsOffset] = u8 +- **Bit-size**: 120 + + +### `NOTEHASHEXISTS` +Check whether a note hash exists in the note hash tree (as of the start of the current block) + +[See in table.](#isa-table-notehashexists) + +- **Opcode**: 0x2d +- **Category**: World State - Notes & Nullifiers +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **leafOffset**: memory offset of the leaf + - **leafIndexOffset**: memory offset of the leaf index + - **existsOffset**: memory offset specifying where to store operation's result (whether the archive leaf exists) +- **Expression**: + +{`exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.storageAddress, M[leafOffset]), +}) +M[existsOffset] = exists`} + +- **World State access tracing**: + +{`context.worldStateAccessTrace.noteHashChecks.append( + TracedLeafCheck { + callPointer: context.environment.callPointer, + leafIndex: M[leafIndexOffset] + leaf: M[leafOffset], + exists: exists, // defined above } )`} - **Triggers downstream circuit operations**: Note hash siloing (hash with storage contract address), note hash tree membership check -- **Tag updates**: `T[dstOffset] = u8` +- **Tag updates**: `T[existsOffset] = u8` - **Bit-size**: 120 @@ -1355,7 +1420,7 @@ Emit a new note hash to be inserted into the note hash tree [See in table.](#isa-table-emitnotehash) -- **Opcode**: 0x2d +- **Opcode**: 0x2e - **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1373,7 +1438,7 @@ Emit a new note hash to be inserted into the note hash tree TracedNoteHash { callPointer: context.environment.callPointer, value: M[noteHashOffset], // unsiloed note hash - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } )`} @@ -1387,7 +1452,7 @@ Check whether a nullifier exists in the nullifier tree (including nullifiers fro [See in table.](#isa-table-nullifierexists) -- **Opcode**: 0x2e +- **Opcode**: 0x2f - **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1408,12 +1473,12 @@ M[existsOffset] = exists`} callPointer: context.environment.callPointer, leaf: M[nullifierOffset], exists: exists, // defined above - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } )`} - **Triggers downstream circuit operations**: Nullifier siloing (hash with storage contract address), nullifier tree membership check -- **Tag updates**: `T[dstOffset] = u8` +- **Tag updates**: `T[existsOffset] = u8` - **Bit-size**: 88 @@ -1422,7 +1487,7 @@ Emit a new nullifier to be inserted into the nullifier tree [See in table.](#isa-table-emitnullifier) -- **Opcode**: 0x2f +- **Opcode**: 0x30 - **Category**: World State - Notes & Nullifiers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1440,7 +1505,7 @@ Emit a new nullifier to be inserted into the nullifier tree TracedNullifier { callPointer: context.environment.callPointer, value: M[nullifierOffset], // unsiloed nullifier - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } )`} @@ -1450,11 +1515,11 @@ Emit a new nullifier to be inserted into the nullifier tree [![](./images/bit-formats/EMITNULLIFIER.png)](./images/bit-formats/EMITNULLIFIER.png) ### `READL1TOL2MSG` -Check if a message exists in the L1-to-L2 message tree and reads it if so. +Check if a message exists in the L1-to-L2 message tree and reads it if so [See in table.](#isa-table-readl1tol2msg) -- **Opcode**: 0x30 +- **Opcode**: 0x31 - **Category**: World State - Messaging - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1489,41 +1554,56 @@ if exists: - **Additional AVM circuit checks**: `msgKey == sha256_to_field(msg)` - **Triggers downstream circuit operations**: L1-to-L2 message tree membership check -- **Tag updates**: `T[dstOffset:dstOffset+msgSize] = field` +- **Tag updates**: + +{`T[existsOffset] = u8, +T[dstOffset:dstOffset+msgSize] = field`} + - **Bit-size**: 184 [![](./images/bit-formats/READL1TOL2MSG.png)](./images/bit-formats/READL1TOL2MSG.png) ### `HEADERMEMBER` -Retrieve one member from a specified block's header. Revert if header does not yet exist. See ["Archive"](../state/archive) for more. +Check if a header exists in the [archive tree](../state/archive) and retrieve the specified member if so [See in table.](#isa-table-headermember) -- **Opcode**: 0x31 +- **Opcode**: 0x32 - **Category**: World State - Archive Tree & Headers - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: - **blockIndexOffset**: memory offset of the block index (same as archive tree leaf index) of the header to access - **memberIndexOffset**: memory offset of the index of the member to retrieve from the header of the specified block + - **existsOffset**: memory offset specifying where to store operation's result (whether the leaf exists in the archive tree) - **dstOffset**: memory offset specifying where to store operation's result (the retrieved header member) - **Expression**: -{`M[dstOffset] = context.worldState.headers.get(M[blockIndexOffset])[M[memberIndexOffset]]`} +{`exists = context.worldState.header.has({ + leafIndex: M[blockIndexOffset], leaf: M[msgKeyOffset] +}) +M[existsOffset] = exists +if exists: + header = context.worldState.headers.get(M[blockIndexOffset]) + M[dstOffset] = header[M[memberIndexOffset]] // member`} - **World State access tracing**: {`context.worldStateAccessTrace.archiveChecks.append( TracedArchiveLeafCheck { leafIndex: M[blockIndexOffset], // leafIndex == blockIndex - leaf: hash(context.worldState.headers.get(M[blockIndexOffset])), + leaf: exists ? hash(header) : 0, // "exists" defined above } )`} - **Additional AVM circuit checks**: Hashes entire header to archive leaf for tracing. Aggregates header accesses and so that a header need only be hashed once. - **Triggers downstream circuit operations**: Archive tree membership check -- **Tag updates**: `T[dstOffset] = field` -- **Bit-size**: 120 +- **Tag updates**: + +{`T[existsOffset] = u8 +T[dstOffset] = field`} + +- **Bit-size**: 152 ### `EMITUNENCRYPTEDLOG` @@ -1531,7 +1611,7 @@ Emit an unencrypted log [See in table.](#isa-table-emitunencryptedlog) -- **Opcode**: 0x32 +- **Opcode**: 0x33 - **Category**: Accrued Substate - Logging - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1556,7 +1636,7 @@ Send an L2-to-L1 message [See in table.](#isa-table-sendl2tol1msg) -- **Opcode**: 0x33 +- **Opcode**: 0x34 - **Category**: Accrued Substate - Messaging - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1582,7 +1662,7 @@ Call into another contract [See in table.](#isa-table-call) -- **Opcode**: 0x34 +- **Opcode**: 0x35 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1602,9 +1682,14 @@ Call into another contract M[argsOffset], M[argsSize], M[retOffset], M[retSize])`} -- **Details**: Creates a new (nested) execution context and triggers execution within it until the nested context halts. - Then resumes execution in the current/calling context. A non-existent contract or one with no code will return success. - See ["Nested contract calls"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts. +- **Details**: Creates a new (nested) execution context and triggers execution within that context. + Execution proceeds in the nested context until it reaches a halt at which point + execution resumes in the current/calling context. + A non-existent contract or one with no code will return success. + The expression's syntax above is shorthand. This instruction is + explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + section, which details nested context derivation, gas cost and refunds, handling + a call's results, and world state access tracing. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` - **Tag updates**: @@ -1620,7 +1705,7 @@ Call into another contract, disallowing World State and Accrued Substate modific [See in table.](#isa-table-staticcall) -- **Opcode**: 0x35 +- **Opcode**: 0x36 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1640,7 +1725,11 @@ Call into another contract, disallowing World State and Accrued Substate modific M[argsOffset], M[argsSize], M[retOffset], M[retSize])`} -- **Details**: Same as `CALL`, but disallows World State and Accrued Substate modifications. See ["Nested contract calls"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts. +- **Details**: Same as `CALL`, but disallows World State and Accrued Substate modifications. + The expression's syntax above is shorthand. This instruction is + explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + section, which details nested context derivation, gas cost and refunds, handling + a call's results, and world state access tracing. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` - **Tag updates**: @@ -1651,12 +1740,52 @@ T[retOffset:retOffset+retSize] = field`} [![](./images/bit-formats/STATICCALL.png)](./images/bit-formats/STATICCALL.png) +### `DELEGATECALL` +Call into another contract, but keep the caller's `sender` and `storageAddress` + +[See in table.](#isa-table-delegatecall) + +- **Opcode**: 0x37 +- **Category**: Control Flow - Contract Calls +- **Flags**: + - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. +- **Args**: + - **gasOffset**: offset to three words containing `{l1GasLeft, l2GasLeft, daGasLeft}`: amount of gas to provide to the callee + - **addrOffset**: address of the contract to call + - **argsOffset**: memory offset to args (will become the callee's calldata) + - **argsSize**: number of words to pass via callee's calldata + - **retOffset**: destination memory offset specifying where to store the data returned from the callee + - **retSize**: number of words to copy from data returned by callee + - **successOffset**: destination memory offset specifying where to store the call's success (0: failure, 1: success) +- **Expression**: + +{`M[successOffset] = delegatecall( + M[gasOffset], M[gasOffset+1], M[gasOffset+2], + M[addrOffset], + M[argsOffset], M[argsSize], + M[retOffset], M[retSize])`} + +- **Details**: Same as `CALL`, but `sender` and `storageAddress` remains + the same in the nested call as they were in the caller. + The expression's syntax above is shorthand. This instruction is + explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + section, which details nested context derivation, gas cost and refunds, handling + a call's results, and world state access tracing. +- **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` +- **Tag updates**: + +{`T[successOffset] = u8 +T[retOffset:retOffset+retSize] = field`} + +- **Bit-size**: 248 + + ### `RETURN` Halt execution within this context (without revert), optionally returning some data [See in table.](#isa-table-return) -- **Opcode**: 0x36 +- **Opcode**: 0x38 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. @@ -1678,7 +1807,7 @@ Halt execution within this context as `reverted`, optionally returning some data [See in table.](#isa-table-revert) -- **Opcode**: 0x37 +- **Opcode**: 0x39 - **Category**: Control Flow - Contract Calls - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. diff --git a/yellow-paper/docs/public-vm/instruction-set.mdx b/yellow-paper/docs/public-vm/instruction-set.mdx index 3b751dd3984..6cf1874cb55 100644 --- a/yellow-paper/docs/public-vm/instruction-set.mdx +++ b/yellow-paper/docs/public-vm/instruction-set.mdx @@ -4,6 +4,7 @@ This page lists all of the instructions supported by the Aztec Virtual Machine ( The following notes are relevant to the table and sections below: - `M[offset]` notation is shorthand for `context.machineState.memory[offset]` +- `S[slot]` notation is shorthand for an access to the specified slot in the current contract's public storage (`context.worldState.publicStorage`) after the slot has been siloed by the storage address (`hash(context.environment.storageAddress, slot)`) - Any instruction whose description does not mention a program counter change simply increments it: `context.machineState.pc++` - All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./avm#gas-limits-and-tracking) - Any instruction can lead to an exceptional halt as specified in ["Exceptional halting"](./avm#exceptional-halting) diff --git a/yellow-paper/docs/public-vm/nested-calls.md b/yellow-paper/docs/public-vm/nested-calls.md new file mode 100644 index 00000000000..8685775f99b --- /dev/null +++ b/yellow-paper/docs/public-vm/nested-calls.md @@ -0,0 +1,150 @@ +# Nested contract calls + +## Nested Call Instructions + +The AVM supports three types of contract calls: [`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), and [`DELEGATECALL`](./instruction-set#isa-section-delegatecall). + +These call instructions share the same argument definitions: `gasOffset`, `addrOffset`, `argsOffset`, `argsSize`, `retOffset`, `retSize`, and `successOffset`. These arguments will be referred to via those keywords below, and will often be used in conjunction with the `M[offset]` syntax which is shorthand for `context.machineState.memory[offset]`. + +The terms `isStaticCall` and `isDelegateCall` used below simply refer to the call instruction's type (`instr.opcode == STATICCALL`, `instr.opcode == DELEGATECALL`). + +## Tracing nested contract calls + +Before nested execution begins, the contract call is traced. +```jsx +context.worldStateAccessTrace.contractCalls.append( + TracedContractCall { + callPointer: context.worldStateAccessTrace.contractCalls.length + 1, + address: M[addrOffset], + storageAddress: M[addrOffset], + counter: context.worldStateAccessTrace.accessCounter++, + endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts + } +) +``` + +## Context initialization for nested calls + +The contract being called into is referenced as `contract` in the following definitions: +```jsx +contract = callingContext.worldState.contracts[M[addrOffset]] +``` + +A nested contract call's execution environment and machine state are derived from the caller's context and the call instruction's arguments: + +```jsx +nestedExecutionEnvironment = ExecutionEnvironment { + origin: context.origin, + sender: isDelegateCall ? context.sender : context.address, + address: M[addrOffset], + storageAddress: isDelegateCall ? context.storageAddress : M[addrOffset], + portal: contract.portal, + feePerL1Gas: context.environment.feePerL1Gas, + feePerL2Gas: context.environment.feePerL2Gas, + feePerDaGas: context.environment.feePerDaGas, + contractCallDepth: context.contractCallDepth + 1, + contractCallPointer: context.worldStateAccessTrace.contractCalls.length + 1, + globals: context.globals, + isStaticCall: isStaticCall, + isDelegateCall: isDelegateCall, + calldata: context.memory[M[argsOffset]:M[argsOffset]+argsSize], + bytecode: contract.bytecode, +} +nestedMachineState = MachineState { + l1GasLeft: context.machineState.memory[M[gasOffset]], + l2GasLeft: context.machineState.memory[M[gasOffset+1]], + daGasLeft: context.machineState.memory[M[gasOffset+2]], + pc = 0, + internalCallStack = [], // initialized as empty + memory = [0, ..., 0], // all 2^32 entries are initialized to zero +} +``` + +The nested call's execution context is then initialized: +```jsx +nestedContext = AvmContext { + environment: nestedExecutionEnvironment, + machineState: nestedMachineState, + worldState: context.worldState, + worldStateAccessTrace: context.worldStateAccessTrace, + accruedSubstate: { [], ... [], }, // all empty + results: {reverted: false, output: []}, +} +``` + +## Gas cost of call instruction + +A call instruction's gas cost is derived from its `gasOffset` argument. In other words, the caller "allocates" gas for a nested call via its `gasOffset` argument. + +| Cost Term | Value | +| --- | --- | +| `l1GasCost` | `M[gasOffset]` | +| `l2GasCost` | `M[gasOffset+1]` | +| `daGasCost` | `M[gasOffset+2]` | + +As with all instructions, gas is checked and cost is deducted _prior_ to the instruction's execution. +```jsx +assert context.machineState.l1GasLeft - l1GasCost > 0 +assert context.machineState.l2GasLeft - l2GasCost > 0 +assert context.machineState.daGasLeft - daGasCost > 0 +context.l1GasLeft -= l1GasCost +context.l2GasLeft -= l2GasCost +context.daGasLeft -= daGasCost +``` + +When the nested call halts, it may not have used up its entire gas allocation. Any unused gas is refunded to the caller as expanded on in ["Updating the calling context after nested call halts"](#updating-the-calling-context-after-nested-call-halts). + +## Nested execution + +Once the execution context is initialized, execution begins in the nested context. Note that this will modify the nested context. +```jsx +execute(nestedContext) +``` + +## Updating the calling context after nested call halts + +The caller checks whether the nested call succeeded, and places the answer in memory. +```jsx +context.machineState.memory[instr.args.successOffset] = !nestedContext.results.reverted +``` + +Any unused gas is refunded to the caller. +```jsx +context.l1GasLeft += nestedContext.machineState.l1GasLeft +context.l2GasLeft += nestedContext.machineState.l2GasLeft +context.daGasLeft += nestedContext.machineState.daGasLeft +``` + +If the call instruction specifies non-zero `retSize`, the caller copies any returned output data to its memory. +```jsx +if retSize > 0: + context.memory[retOffset:retOffset+retSize] = nestedContext.results.output +``` + +If the nested call succeeded, the caller accepts its world state and accrued substate modifications. +```jsx +if !nestedContext.results.reverted: + context.worldState = nestedContext.worldState + context.accruedSubstate.append(nestedContext.accruedSubstate) +``` + +## Accepting nested call's World State access trace + +If the nested call reverted, the caller initializes the "end-lifetime" of all world state accesses made within the nested call. +```jsx +if nestedContext.results.reverted: + // process all traces (this is shorthand) + for trace in nestedContext.worldStateAccessTrace: + for access in trace: + if access.callPointer >= nestedContext.environment.callPointer: + // don't override end-lifetime already set by a deeper nested call + if access.endLifetime == 0: + access.endLifetime = nestedContext.worldStateAccessTrace.accessCounter +``` + +> A world state access that was made in a deeper nested _reverted_ context will already have its end-lifetime initialized. The caller does _not_ overwrite this access' end-lifetime here as it already has a narrower lifetime. + +Regardless of whether the nested call reverted, the caller accepts its updated world state access trace (with updated lifetimes). +```jsx +context.worldStateAccessTrace = nestedContext.worldStateAccessTrace +``` diff --git a/yellow-paper/docs/public-vm/state.md b/yellow-paper/docs/public-vm/state.md index 78976f63e76..0295c049d29 100644 --- a/yellow-paper/docs/public-vm/state.md +++ b/yellow-paper/docs/public-vm/state.md @@ -23,15 +23,16 @@ This section describes the types of state maintained by the AVM. [Aztec's global state](../state) is implemented as a few merkle trees. These trees are exposed to the AVM as follows: -| State | Tree | Merkle Tree Type | AVM Access | -| --- | --- | --- | --- | -| Public Storage | Public Data Tree | Updatable | membership-checks (latest), reads, writes | +| State | Tree | Merkle Tree Type | AVM Access | +| --- | --- | --- | --- | +| Public Storage | Public Data Tree | Updatable | membership-checks (latest), reads, writes | | Note Hashes | Note Hash Tree | Append-only | membership-checks (start-of-block), appends | -| Nullifiers | Nullifier Tree | Indexed | membership-checks (latest), appends | +| Nullifiers | Nullifier Tree | Indexed | membership-checks (latest), appends | | L1-to-L2 Messages | L1-to-L2 Message Tree | Append-only | membership-checks (start-of-block), leaf-preimage-reads | | Headers | Archive Tree | Append-only | membership-checks, leaf-preimage-reads | +| Contracts\* | - | - | - | -> As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. +> \* As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. ### AVM World State @@ -45,11 +46,16 @@ The following table defines an AVM context's world state interface: | Field | AVM Instructions & Access | | --- | --- | -| `publicStorage` | [`SLOAD`](./instruction-set#isa-section-sload) (membership-checks (latest) & reads), [`SSTORE`](./instruction-set#isa-section-sstore) (writes) | +| `contracts` | [`*CALL`](./instruction-set#isa-section-call) (special case, see below\*) | +| `publicStorage` | [`SLOAD`](./instruction-set#isa-section-sload) (membership-checks (latest) & reads), [`SSTORE`](./instruction-set#isa-section-sstore) (writes) | | `noteHashes` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) (membership-checks (start-of-block)), [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) (appends) | -| `nullifiers` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) membership-checks (latest), [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) (appends) | -| `l1ToL2Messages` | [`READL1TOL2MSG`](./instruction-set#isa-section-readl1tol2msg) (membership-checks (start-of-block) & leaf-preimage-reads) | -| `headers` | [`HEADERMEMBER`](./instruction-set#isa-section-headermember) (membership-checks & leaf-preimage-reads) | +| `nullifiers` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) membership-checks (latest), [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) (appends) | +| `l1ToL2Messages` | [`READL1TOL2MSG`](./instruction-set#isa-section-readl1tol2msg) (membership-checks (start-of-block) & leaf-preimage-reads) | +| `headers` | [`HEADERMEMBER`](./instruction-set#isa-section-headermember) (membership-checks & leaf-preimage-reads) | + +> \* `*CALL` is short for `CALL`/`STATICCALL`/`DELEGATECALL`. + +> \* For the purpose of the AVM, the world state's `contracts` entry is readable to retrieve bytecode on context-creation, and it is effectively updated when a new nullifier is created for a contract class identifier or contract address. ### World State Access Trace @@ -63,17 +69,18 @@ This trace of an AVM session's contract calls and world state accesses is named Each entry in the world state access trace is listed below along with its type and the instructions that append to it: -| Trace | Relevant State | Trace Vector Type | Instructions | -| --- | --- | --- | --- | -| `publicStorageReads` | Public Storage | `Vector` | [`SLOAD`](./instruction-set#isa-section-sload) | -| `publicStorageWrites` | Public Storage | `Vector` | [`SSTORE`](./instruction-set#isa-section-sstore) | -| `noteHashChecks` | Note Hashes | `Vector` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) | -| `newNoteHashes` | Note Hashes | `Vector` | [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) | +| Field | Relevant State | Type | Instructions | +| --- | --- | --- | --- | +| `accessCounter` | all state | `field` | incremented by all instructions below | +| `contractCalls` | Contracts | `Vector` | [`*CALL`](./instruction-set#isa-section-call) | +| `publicStorageReads` | Public Storage | `Vector` | [`SLOAD`](./instruction-set#isa-section-sload) | +| `publicStorageWrites` | Public Storage | `Vector` | [`SSTORE`](./instruction-set#isa-section-sstore) | +| `noteHashChecks` | Note Hashes | `Vector` | [`NOTEHASHEXISTS`](./instruction-set#isa-section-notehashexists) | +| `newNoteHashes` | Note Hashes | `Vector` | [`EMITNOTEHASH`](./instruction-set#isa-section-emitnotehash) | | `nullifierChecks` | Nullifiers | `Vector` | [`NULLIFIERSEXISTS`](./instruction-set#isa-section-nullifierexists) | -| `newNullifiers` | Nullifiers | `Vector` | [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) | -| `l1ToL2MessageReads` | L1-To-L2 Messages | `Vector` | [`READL1TOL2MSG`](./instruction-set#isa-section-readl1tol2msg) | -| `archiveChecks` | Headers | `Vector` | [`HEADERMEMBER`](./instruction-set#isa-section-headermember) | -| `contractCalls` | - | `Vector` | [`*CALL`](./instruction-set#isa-section-call) | +| `newNullifiers` | Nullifiers | `Vector` | [`EMITNULLIFIER`](./instruction-set#isa-section-emitnullifier) | +| `l1ToL2MessageReads` | L1-To-L2 Messages | `Vector` | [`READL1TOL2MSG`](./instruction-set#isa-section-readl1tol2msg) | +| `archiveChecks` | Headers | `Vector` | [`HEADERMEMBER`](./instruction-set#isa-section-headermember) | > The types tracked in these trace vectors are defined [here](./type-structs). diff --git a/yellow-paper/docs/public-vm/type-structs.md b/yellow-paper/docs/public-vm/type-structs.md index 122821a487a..2b1793b19cc 100644 --- a/yellow-paper/docs/public-vm/type-structs.md +++ b/yellow-paper/docs/public-vm/type-structs.md @@ -6,9 +6,11 @@ This section lists type definitions relevant to AVM State and Circuit I/O. | Field | Type | Description | | --- | --- | --- | +| `callPointer` | `field` | The call pointer assigned to this call. | | `address` | `field` | The called contract address. | | `storageAddress` | `field` | The storage contract address (different from `address` for delegate calls). | -| `endLifetime` | `field` | End lifetime of a call. Final `clk` for reverted calls, `endLifetime` of parent for successful calls. Successful initial/top-level calls have infinite (max-value) `endLifetime`. | +| `counter` | `field` | When did this occur relative to other world state accesses. | +| `endLifetime` | `field` | End lifetime of a call. Final `accessCounter` for reverted calls, `endLifetime` of parent for successful calls. Successful initial/top-level calls have infinite (max-value) `endLifetime`. | #### _TracedL1ToL2MessageRead_ diff --git a/yellow-paper/sidebars.js b/yellow-paper/sidebars.js index 4d3ae29ae54..f3832acd596 100644 --- a/yellow-paper/sidebars.js +++ b/yellow-paper/sidebars.js @@ -189,15 +189,17 @@ const sidebars = { type: "category", link: { type: "doc", id: "public-vm/avm" }, items: [ - "public-vm/avm", + "public-vm/introduction", "public-vm/state", - "public-vm/type-structs", "public-vm/memory-model", + "public-vm/execution", + "public-vm/nested-calls", "public-vm/instruction-set", "public-vm/avm-circuit", "public-vm/control-flow", "public-vm/alu", "public-vm/bytecode-validation-circuit", + "public-vm/type-structs", ], }, ], diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 266ceaaf28d..368c3ca0bac 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -12,6 +12,21 @@ const IN_TAG_DESCRIPTION_NO_FIELD = IN_TAG_DESCRIPTION + " `field` type is NOT s const DST_TAG_DESCRIPTION = "The [tag/size](./memory-model#tags-and-tagged-memory) to tag the destination with but not to check inputs against."; const INDIRECT_FLAG_DESCRIPTION = "Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`."; +const CALL_INSTRUCTION_ARGS = [ + {"name": "gasOffset", "description": "offset to three words containing `{l1GasLeft, l2GasLeft, daGasLeft}`: amount of gas to provide to the callee"}, + {"name": "addrOffset", "description": "address of the contract to call"}, + {"name": "argsOffset", "description": "memory offset to args (will become the callee's calldata)"}, + {"name": "argsSize", "description": "number of words to pass via callee's calldata", "mode": "immediate", "type": "u32"}, + {"name": "retOffset", "description": "destination memory offset specifying where to store the data returned from the callee"}, + {"name": "retSize", "description": "number of words to copy from data returned by callee", "mode": "immediate", "type": "u32"}, + {"name": "successOffset", "description": "destination memory offset specifying where to store the call's success (0: failure, 1: success)", "type": "u8"}, +]; +const CALL_INSTRUCTION_DETAILS = ` + The expression's syntax above is shorthand. This instruction is + explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + section, which details nested context derivation, gas cost and refunds, handling + a call's results, and world state access tracing.`; + const INSTRUCTION_SET_RAW = [ { "id": "add", @@ -741,11 +756,11 @@ context.machineState.pc = loc {"name": "dstOffset", "description": "memory offset specifying where to store operation's result"}, ], "Expression": ` -M[dstOffset] = context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] +M[dstOffset] = S[M[slotOffset]] `, "Summary": "Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots.", "Details": ` -// Expression is short-hand for +// Expression is shorthand for leafIndex = hash(context.environment.storageAddress, M[slotOffset]) exists = context.worldState.publicStorage.has(leafIndex) // exists == previously-written if exists: @@ -761,7 +776,7 @@ context.worldStateAccessTrace.publicStorageReads.append( slot: M[slotOffset], exists: exists, // defined above value: value, // defined above - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } ) `, @@ -781,11 +796,11 @@ context.worldStateAccessTrace.publicStorageReads.append( {"name": "slotOffset", "description": "memory offset containing the storage slot to store to"}, ], "Expression": ` -context.worldState.publicStorage[context.environment.storageAddress][M[slotOffset]] = M[srcOffset] +S[M[slotOffset]] = M[srcOffset] `, "Summary": "Write a word to this contract's persistent public storage", "Details": ` -// Expression is short-hand for +// Expression is shorthand for context.worldState.publicStorage.set({ leafIndex: hash(context.environment.storageAddress, M[slotOffset]), leaf: M[srcOffset], @@ -797,7 +812,7 @@ context.worldStateAccessTrace.publicStorageWrites.append( callPointer: context.environment.callPointer, slot: M[slotOffset], value: M[srcOffset], - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } ) `, @@ -832,13 +847,47 @@ context.worldStateAccessTrace.noteHashChecks.append( leafIndex: M[leafIndexOffset] leaf: M[leafOffset], exists: exists, // defined above - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, + } +) +`, + "Triggers downstream circuit operations": "Storage slot siloing (hash with contract address), public data tree update", + "Tag checks": "", + "Tag updates": "T[existsOffset] = u8", + }, + { + "id": "notehashexists", + "Name": "`NOTEHASHEXISTS`", + "Category": "World State - Notes & Nullifiers", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + ], + "Args": [ + {"name": "leafOffset", "description": "memory offset of the leaf"}, + {"name": "leafIndexOffset", "description": "memory offset of the leaf index"}, + {"name": "existsOffset", "description": "memory offset specifying where to store operation's result (whether the archive leaf exists)"}, + ], + "Expression": ` +exists = context.worldState.noteHashes.has({ + leafIndex: M[leafIndexOffset] + leaf: hash(context.environment.storageAddress, M[leafOffset]), +}) +M[existsOffset] = exists +`, + "Summary": "Check whether a note hash exists in the note hash tree (as of the start of the current block)", + "World State access tracing": ` +context.worldStateAccessTrace.noteHashChecks.append( + TracedLeafCheck { + callPointer: context.environment.callPointer, + leafIndex: M[leafIndexOffset] + leaf: M[leafOffset], + exists: exists, // defined above } ) `, "Triggers downstream circuit operations": "Note hash siloing (hash with storage contract address), note hash tree membership check", "Tag checks": "", - "Tag updates": "`T[dstOffset] = u8`", + "Tag updates": "`T[existsOffset] = u8`", }, { "id": "emitnotehash", @@ -861,7 +910,7 @@ context.worldStateAccessTrace.newNoteHashes.append( TracedNoteHash { callPointer: context.environment.callPointer, value: M[noteHashOffset], // unsiloed note hash - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } ) `, @@ -893,13 +942,13 @@ context.worldStateAccessTrace.nullifierChecks.append( callPointer: context.environment.callPointer, leaf: M[nullifierOffset], exists: exists, // defined above - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } ) `, "Triggers downstream circuit operations": "Nullifier siloing (hash with storage contract address), nullifier tree membership check", "Tag checks": "", - "Tag updates": "`T[dstOffset] = u8`", + "Tag updates": "`T[existsOffset] = u8`", }, { "id": "emitnullifier", @@ -922,7 +971,7 @@ context.worldStateAccessTrace.newNullifiers.append( TracedNullifier { callPointer: context.environment.callPointer, value: M[nullifierOffset], // unsiloed nullifier - counter: clk, + counter: context.worldStateAccessTrace.accessCounter++, } ) `, @@ -954,7 +1003,7 @@ if exists: leafIndex: M[msgLeafIndex], leaf: M[msgKeyOffset] }) `, - "Summary": "Check if a message exists in the L1-to-L2 message tree and reads it if so.", + "Summary": "Check if a message exists in the L1-to-L2 message tree and reads it if so", "World State access tracing": ` context.worldStateAccessTrace.l1ToL2MessagesReads.append( ReadL1ToL2Message { @@ -969,7 +1018,10 @@ context.worldStateAccessTrace.l1ToL2MessagesReads.append( "Additional AVM circuit checks": "`msgKey == sha256_to_field(msg)`", "Triggers downstream circuit operations": "L1-to-L2 message tree membership check", "Tag checks": "", - "Tag updates": "`T[dstOffset:dstOffset+msgSize] = field`", + "Tag updates": ` +T[existsOffset] = u8, +T[dstOffset:dstOffset+msgSize] = field +`, }, { "id": "headermember", @@ -981,24 +1033,34 @@ context.worldStateAccessTrace.l1ToL2MessagesReads.append( "Args": [ {"name": "blockIndexOffset", "description": "memory offset of the block index (same as archive tree leaf index) of the header to access"}, {"name": "memberIndexOffset", "description": "memory offset of the index of the member to retrieve from the header of the specified block"}, + {"name": "existsOffset", "description": "memory offset specifying where to store operation's result (whether the leaf exists in the archive tree)"}, {"name": "dstOffset", "description": "memory offset specifying where to store operation's result (the retrieved header member)"}, ], "Expression": ` -M[dstOffset] = context.worldState.headers.get(M[blockIndexOffset])[M[memberIndexOffset]] +exists = context.worldState.header.has({ + leafIndex: M[blockIndexOffset], leaf: M[msgKeyOffset] +}) +M[existsOffset] = exists +if exists: + header = context.worldState.headers.get(M[blockIndexOffset]) + M[dstOffset] = header[M[memberIndexOffset]] // member `, - "Summary": "Retrieve one member from a specified block's header. Revert if header does not yet exist. See [\"Archive\"](../state/archive) for more.", + "Summary": "Check if a header exists in the [archive tree](../state/archive) and retrieve the specified member if so", "World State access tracing": ` context.worldStateAccessTrace.archiveChecks.append( TracedArchiveLeafCheck { leafIndex: M[blockIndexOffset], // leafIndex == blockIndex - leaf: hash(context.worldState.headers.get(M[blockIndexOffset])), + leaf: exists ? hash(header) : 0, // "exists" defined above } ) `, "Additional AVM circuit checks": "Hashes entire header to archive leaf for tracing. Aggregates header accesses and so that a header need only be hashed once.", "Triggers downstream circuit operations": "Archive tree membership check", "Tag checks": "", - "Tag updates": "`T[dstOffset] = field`", + "Tag updates": ` +T[existsOffset] = u8 +T[dstOffset] = field +`, }, { "id": "emitunencryptedlog", @@ -1054,15 +1116,7 @@ context.accruedSubstate.sentL2ToL1Messages.append( "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], - "Args": [ - {"name": "gasOffset", "description": "offset to three words containing `{l1GasLeft, l2GasLeft, daGasLeft}`: amount of gas to provide to the callee"}, - {"name": "addrOffset", "description": "address of the contract to call"}, - {"name": "argsOffset", "description": "memory offset to args (will become the callee's calldata)"}, - {"name": "argsSize", "description": "number of words to pass via callee's calldata", "mode": "immediate", "type": "u32"}, - {"name": "retOffset", "description": "destination memory offset specifying where to store the data returned from the callee"}, - {"name": "retSize", "description": "number of words to copy from data returned by callee", "mode": "immediate", "type": "u32"}, - {"name": "successOffset", "description": "destination memory offset specifying where to store the call's success (0: failure, 1: success)", "type": "u8"}, - ], + "Args": CALL_INSTRUCTION_ARGS, "Expression":` M[successOffset] = call( M[gasOffset], M[gasOffset+1], M[gasOffset+2], @@ -1071,9 +1125,11 @@ M[successOffset] = call( M[retOffset], M[retSize]) `, "Summary": "Call into another contract", - "Details": `Creates a new (nested) execution context and triggers execution within it until the nested context halts. - Then resumes execution in the current/calling context. A non-existent contract or one with no code will return success. - See [\"Nested contract calls\"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts.`, + "Details": `Creates a new (nested) execution context and triggers execution within that context. + Execution proceeds in the nested context until it reaches a halt at which point + execution resumes in the current/calling context. + A non-existent contract or one with no code will return success. ` + + CALL_INSTRUCTION_DETAILS, "Tag checks": "`T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32`", "Tag updates": ` T[successOffset] = u8 @@ -1087,15 +1143,7 @@ T[retOffset:retOffset+retSize] = field "Flags": [ {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], - "Args": [ - {"name": "gasOffset", "description": "offset to three words containing `{l1GasLeft, l2GasLeft, daGasLeft}`: amount of gas to provide to the callee"}, - {"name": "addrOffset", "description": "address of the contract to call"}, - {"name": "argsOffset", "description": "memory offset to args (will become the callee's calldata)"}, - {"name": "argsSize", "description": "number of words to pass via callee's calldata", "mode": "immediate", "type": "u32"}, - {"name": "retOffset", "description": "destination memory offset specifying where to store the data returned from the callee"}, - {"name": "retSize", "description": "number of words to copy from data returned by callee", "mode": "immediate", "type": "u32"}, - {"name": "successOffset", "description": "destination memory offset specifying where to store the call's success (0: failure, 1: success)", "type": "u8"}, - ], + "Args": CALL_INSTRUCTION_ARGS, "Expression": ` M[successOffset] = staticcall( M[gasOffset], M[gasOffset+1], M[gasOffset+2], @@ -1104,7 +1152,33 @@ M[successOffset] = staticcall( M[retOffset], M[retSize]) `, "Summary": "Call into another contract, disallowing World State and Accrued Substate modifications", - "Details": "Same as `CALL`, but disallows World State and Accrued Substate modifications. See [\"Nested contract calls\"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts.", + "Details": `Same as \`CALL\`, but disallows World State and Accrued Substate modifications. ` + + CALL_INSTRUCTION_DETAILS, + "Tag checks": "`T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32`", + "Tag updates": ` +T[successOffset] = u8 +T[retOffset:retOffset+retSize] = field +`, + }, + { + "id": "delegatecall", + "Name": "`DELEGATECALL`", + "Category": "Control Flow - Contract Calls", + "Flags": [ + {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, + ], + "Args": CALL_INSTRUCTION_ARGS, + "Expression": ` +M[successOffset] = delegatecall( + M[gasOffset], M[gasOffset+1], M[gasOffset+2], + M[addrOffset], + M[argsOffset], M[argsSize], + M[retOffset], M[retSize]) +`, + "Summary": "Call into another contract, but keep the caller's `sender` and `storageAddress`", + "Details": `Same as \`CALL\`, but \`sender\` and \`storageAddress\` remains + the same in the nested call as they were in the caller. ` + + CALL_INSTRUCTION_DETAILS, "Tag checks": "`T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32`", "Tag updates": ` T[successOffset] = u8 From 6ba4e7cb90adae68c4a1f21df6dd9789755cc991 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 21:41:46 +0000 Subject: [PATCH 08/21] rename --- .../public-vm/gen/{_InstructionSet.mdx => _instruction-set.mdx} | 0 yellow-paper/docs/public-vm/instruction-set.mdx | 2 +- yellow-paper/src/preprocess/InstructionSet/genMarkdown.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename yellow-paper/docs/public-vm/gen/{_InstructionSet.mdx => _instruction-set.mdx} (100%) diff --git a/yellow-paper/docs/public-vm/gen/_InstructionSet.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx similarity index 100% rename from yellow-paper/docs/public-vm/gen/_InstructionSet.mdx rename to yellow-paper/docs/public-vm/gen/_instruction-set.mdx diff --git a/yellow-paper/docs/public-vm/instruction-set.mdx b/yellow-paper/docs/public-vm/instruction-set.mdx index 6cf1874cb55..2af70937b98 100644 --- a/yellow-paper/docs/public-vm/instruction-set.mdx +++ b/yellow-paper/docs/public-vm/instruction-set.mdx @@ -11,6 +11,6 @@ The following notes are relevant to the table and sections below: - The term `hash` used in expressions below represents a Poseidon hash operation. - Type structures used in world state tracing operations are defined in ["Type Definitions"](./type-structs) -import GeneratedInstructionSet from "./gen/_InstructionSet.mdx"; +import GeneratedInstructionSet from "./gen/_instruction-set.mdx"; diff --git a/yellow-paper/src/preprocess/InstructionSet/genMarkdown.js b/yellow-paper/src/preprocess/InstructionSet/genMarkdown.js index fbb9792b111..4070fdc76a5 100644 --- a/yellow-paper/src/preprocess/InstructionSet/genMarkdown.js +++ b/yellow-paper/src/preprocess/InstructionSet/genMarkdown.js @@ -118,7 +118,7 @@ async function generateInstructionSet() { const rootDir = path.join(__dirname, "../../../"); const docsDir = path.join(rootDir, "docs", "docs"); - const relPath = path.relative(docsDir, "docs/public-vm/gen/_InstructionSet.mdx"); + const relPath = path.relative(docsDir, "docs/public-vm/gen/_instruction-set.mdx"); const docsFilePath = path.resolve(docsDir, relPath); const docsDirName = path.dirname(docsFilePath); if (!fs.existsSync(docsDirName)) { From c5a8fc79d3c4d6639d82c66227c3ff4522533d4b Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 22:03:33 +0000 Subject: [PATCH 09/21] move things around, fix links --- yellow-paper/docs/public-vm/avm-circuit.md | 6 +- yellow-paper/docs/public-vm/avm.md | 480 ------------------ yellow-paper/docs/public-vm/context.md | 119 +++++ yellow-paper/docs/public-vm/execution.md | 190 +++++++ .../docs/public-vm/gen/_instruction-set.mdx | 10 +- yellow-paper/docs/public-vm/intro.md | 43 ++ yellow-paper/sidebars.js | 7 +- .../InstructionSet/InstructionSet.js | 6 +- 8 files changed, 367 insertions(+), 494 deletions(-) delete mode 100644 yellow-paper/docs/public-vm/avm.md create mode 100644 yellow-paper/docs/public-vm/context.md create mode 100644 yellow-paper/docs/public-vm/execution.md create mode 100644 yellow-paper/docs/public-vm/intro.md diff --git a/yellow-paper/docs/public-vm/avm-circuit.md b/yellow-paper/docs/public-vm/avm-circuit.md index d4c3c17e653..256ab1b422f 100644 --- a/yellow-paper/docs/public-vm/avm-circuit.md +++ b/yellow-paper/docs/public-vm/avm-circuit.md @@ -24,7 +24,7 @@ Prior to the VM circuit's execution, a vector is assembled to contain the byteco Each entry in the bytecode vector will be paired with a call pointer and program counter. This **Bytecode Table** maps a call pointer and program counter to an instruction, and is used by the Instruction Controller to fetch instructions. > Note: "call pointer" is expanded on in a later section. -Each contract's public bytecode is committed to during contract deployment. As part of the AVM circuit verification algorithm, the bytecode vector (as a concatenation of all relevant contract bytecodes) is verified against the corresponding bytecode commitments. This is expanded on in ["Bytecode Validation Circuit"](./bytecode-validation-circuit.md). While the AVM circuit enforces that the correct instructions are executed according to its bytecode table, the verifier checks that bytecode table against the previously validated bytecode commitments. +Each contract's public bytecode is committed to during contract deployment. As part of the AVM circuit verification algorithm, the bytecode vector (as a concatenation of all relevant contract bytecodes) is verified against the corresponding bytecode commitments. This is expanded on in ["Bytecode Validation Circuit"](./bytecode-validation-circuit). While the AVM circuit enforces that the correct instructions are executed according to its bytecode table, the verifier checks that bytecode table against the previously validated bytecode commitments. ## Instruction Controller The Instruction Controller's responsibilities include instruction fetching and decoding. @@ -32,7 +32,7 @@ The Instruction Controller's responsibilities include instruction fetching and d ### Instruction fetching The Instruction Controller's **instruction fetch** mechanism makes use of the bytecode table to determine which instruction to execute based on the call pointer and program counter. Each instruction fetch corresponds to a circuit lookup to enforce that the correct instruction is processed for a given contract and program counter. -The combination of the instruction fetch circuitry, the bytecode table, and the ["Bytecode Validation Circuit"](./bytecode-validation-circuit.md) ensure that VM circuit processes the proper sequence of instructions. +The combination of the instruction fetch circuitry, the bytecode table, and the ["Bytecode Validation Circuit"](./bytecode-validation-circuit) ensure that VM circuit processes the proper sequence of instructions. ### Instruction decoding and sub-operations An instruction (its opcode, flags, and arguments) represents some high-level VM operation. For example, an `ADD` instruction says "add two items from memory and store the result in memory". The Instruction Controller **instruction decode** mechanism decodes instructions into sub-operations. While an instruction likely requires many circuit components, a **sub-operation** is a smaller task that can be fed to just one VM circuit component for processing. By decoding an instruction into sub-operations, the VM circuit translates high-level instructions into smaller achievable tasks. To continue with the `ADD` example, it would translate "add two items from memory and store the result in memory" to "load an item from memory, load another item from memory, add them, and store the result to memory." @@ -192,7 +192,7 @@ AvmSessionPublicInputs { sessionResults: AvmSessionResults, } ``` -> The `ExecutionEnvironment` structure is defined in [the AVM's high level specification](./avm.md). `initialEnvironment` here omits `calldata` and `bytecode`. +> The `ExecutionEnvironment` structure is defined in [the AVM's execution context](./context). `initialEnvironment` here omits `calldata` and `bytecode`. > The `WorldStateAccessTrace` and `AccruedSubstate` types are defined in ["State"](./state). Their vectors are assigned constant/maximum lengths when used as circuit inputs. diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md deleted file mode 100644 index a24d5ec44ff..00000000000 --- a/yellow-paper/docs/public-vm/avm.md +++ /dev/null @@ -1,480 +0,0 @@ -# Aztec Virtual Machine - -:::note reference -Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). -::: - -## Introduction - -An Aztec transaction may include one or more **public execution requests**. A public execution request is a request to execute a specified contract's public bytecode given some arguments. Execution of a contract's public bytecode is performed by the **Aztec Virtual Machine (AVM)**. - -> A public execution request may originate from a public call enqueued by a transaction's private segment ([`enqueuedPublicFunctionCalls`](../calls/enqueued-calls.md)), or from a public [fee preparation](../gas-and-fees#fee-preparation) or [fee distribution](../gas-and-fees#fee-distribution) call. - -In order to execute public contract bytecode, the AVM requires some context. An [**execution context**](#execution-context) contains all information necessary to initiate AVM execution, including the relevant contract's bytecode and all state maintained by the AVM. A **contract call** initializes an execution context and triggers AVM execution within that context. - -Instruction-by-instruction, the AVM [executes](#execution) the bytecode specified in its context. An **instruction** is a bytecode entry that, when executed, modifies the AVM's execution context (in particular its [state](./state)) according to the instruction's definition in the ["AVM Instruction Set"](./instruction-set). Execution within a context ends when the AVM encounters a [**halt**](#halting). - -During execution, additional contract calls may be made. While an [**initial contract call**](#initial-contract-calls) initializes a new execution context directly from a public execution request, a [**nested contract call**](#nested-contract-calls) occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-call), or `DELEGATECALL`). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. - -A **caller** is a contract call's initiator. The caller of an initial contract call is an Aztec sequencer. The caller of a nested contract call is the AVM itself executing in the calling context. - -## Outline - -- [**Public contract bytecode**](#public-contract-bytecode) (aka AVM bytecode) -- [**Execution context**](#execution-context), outlining the AVM's execution context -- [**Execution**](#execution), outlining control flow, gas tracking, normal halting, and exceptional halting -- [**Initial contract calls**](#initial-contract-calls), outlining the initiation of a contract call from a public execution request -- [**Nested contract calls**](#nested-contract-calls), outlining the initiation of a contract call from an instruction as well as the processing of nested execution results, gas refunds, and state reverts - -> This document is meant to provide a high-level definition of the Aztec Virtual Machine as opposed to a specification of its SNARK implementation. The document therefore mostly omits SNARK or circuit-centric verbiage except when particularly relevant to high-level design decisions. - -This document is supplemented by the following resources: -- **[AVM State](./state.md)** -- **[AVM Instruction Set](./instruction-set)** -- **[AVM Memory Model](./memory-model.md)** -- **[AVM Circuit](./avm-circuit.md)** - -## Public contract bytecode - -A contract's public bytecode is a series of execution instructions for the AVM. Refer to the ["AVM Instruction Set"](./instruction-set) for the details of all supported instructions along with how they modify AVM state. - -The entirety of a contract's public code is represented as a single block of bytecode with a maximum of `MAX_PUBLIC_INSTRUCTIONS_PER_CONTRACT` ($2^{15} = 32768$) instructions. The mechanism used to distinguish between different "functions" in an AVM bytecode program is left as a higher-level abstraction (_e.g._ similar to Solidity's concept of a function selector). - -> See the [Bytecode Validation Circuit](./bytecode-validation-circuit.md) to see how a contract's bytecode can be validated and committed to. - -## Execution Context - -:::note REMINDER -Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). -::: - -An **execution context** includes the information and state relevant to a contract call's execution. When a contract call is made, an execution context is initialized as specified in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls) sections. - -#### _AvmContext_ -| Field | Type | -| --- | --- | -| environment | `ExecutionEnvironment` | -| [machineState](./state#machine-state) | `MachineState` | -| [worldState](./state#avm-world-state) | `AvmWorldState` | -| [worldStateAccessTrace](./state#world-state-access-trace) | `WorldStateAccessTrace` | -| [accruedSubstate](./state#accrued-substate) | `AccruedSubstate` | -| results | `ContractCallResults` | - -### Execution Environment - -A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it fully specifies the execution environment. This is expanded on in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls) sections. - -#### _ExecutionEnvironment_ -| Field | Type | Description | -| --- | --- | --- | -| address | `AztecAddress` | | -| storageAddress | `AztecAddress` | | -| origin | `AztecAddress` | | -| sender | `AztecAddress` | | -| portal | `EthAddress` | | -| feePerL1Gas | `field` | | -| feePerL2Gas | `field` | | -| feePerDaGas | `field` | | -| contractCallDepth | `field` | Depth of the current call (how many nested calls deep is it). | -| contractCallPointer | `field` | Uniquely identifies each contract call processed by an AVM session. An initial call is assigned pointer value of 1 (expanded on in the AVM circuit section's ["Call Pointer"](./avm-circuit#call-pointer) subsection). | -| globals | `PublicGlobalVariables` | | -| isStaticCall | `boolean` | | -| isDelegateCall | `boolean` | | -| calldata | `[field; ]` | | - -### Contract Call Results - -When a contract call halts, it sets the context's **contract call results** to communicate results to the caller. - -#### _ContractCallResults_ -| Field | Type | Description | -| --- | --- | --- | -| reverted | `boolean` | | -| output | `[field; ]` | | - -## Execution - -Once an execution context has been initialized for a contract call, the [machine state's](./state#machine-state) program counter determines which instruction the AVM executes. For any contract call, the program counter starts at zero, and so instruction execution begins with the very first entry in a contract's bytecode. - -### Program Counter and Control Flow - -The program counter (`machineState.pc`) determines which instruction the AVM executes next (`instr = environment.bytecode[pc]`). Each instruction's execution updates the program counter in some way, which allows the AVM to progress to the next instruction at each step. - -Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./instruction-set#isa-section-jump), [`JUMPI`](./instruction-set#isa-section-jumpi), [`INTERNALCALL`](./instruction-set#isa-section-internalcall)) modify the program counter based on arguments. - -The `INTERNALCALL` instruction pushes `machineState.pc+1` to `machineState.internalCallStack` and then updates `pc` to the instruction's destination argument (`instr.args.loc`). The `INTERNALRETURN` instruction pops a destination from `machineState.internalCallStack` and assigns the result to `pc`. - -> An instruction will never assign program counter a value from memory (`machineState.memory`). A `JUMP`, `JUMPI`, or `INTERNALCALL` instruction's destination is a constant from the program bytecode. This property allows for easier static program analysis. - -### Gas limits and tracking -> See ["Gas and Fees"](../gas-and-fees) for a deeper dive into Aztec's gas model and for definitions of each type of gas. - -Each instruction has an associated `l1GasCost`, `l2GasCost`, and `daGasCost`. Before an instruction is executed, the VM enforces that there is sufficient gas remaining via the following assertions: -``` -assert machineState.l1GasLeft - instr.l1GasCost > 0 -assert machineState.l2GasLeft - instr.l2GasCost > 0 -assert machineState.daGasLeft - instr.daGasCost > 0 -``` - -> Many instructions (like arithmetic operations) have 0 `l1GasCost` and `daGasCost`. Instructions only incur an L1 or DA cost if they modify the [world state](./state#avm-world-state) or [accrued substate](./state#accrued-substate). - -If these assertions pass, the machine state's gas left is decreased prior to the instruction's core execution: - -``` -machineState.l1GasLeft -= instr.l1GasCost -machineState.l2GasLeft -= instr.l2GasCost -machineState.daGasLeft -= instr.daGasCost -``` - -If either of these assertions _fail_ for an instruction, this triggers an exceptional halt. The gas left is set to 0 and execution reverts. - -``` -machineState.l1GasLeft = 0 -machineState.l2GasLeft = 0 -machineState.daGasLeft = 0 -``` - -> Reverting and exceptional halts are covered in more detail in the ["Halting" section](#halting). - -### Gas cost notes and examples - -An instruction's gas cost is meant to reflect the computational cost of generating a proof of its correct execution. For some instructions, this computational cost changes based on inputs. Here are some examples and important notes: -- [`JUMP`](./instruction-set/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l1GasCost`, `l2GasCost`, and `daGasCost`. -- The [`SET`](./instruction-set/#isa-section-set) instruction operates on a different sized constant (based on its `dstTag`). Therefore, this instruction's gas cost increases with the size of its input. -- Instructions that operate on a data range of a specified "size" scale in cost with that size. An example of this is the [`CALLDATACOPY`](./instruction-set/#isa-section-calldatacopy) argument which copies `copySize` words from `environment.calldata` to `machineState.memory`. -- The [`CALL`](./instruction-set/#isa-section-call)/[`STATICCALL`](./instruction-set/#isa-section-call)/`DELEGATECALL` instruction's gas cost is determined by its `*Gas` arguments, but any gas unused by the nested contract call's execution is refunded after its completion ([more on this later](#updating-the-calling-context-after-nested-call-halts)). -- An instruction with "offset" arguments (like [`ADD`](./instruction-set/#isa-section-add) and many others), has increased cost for each offset argument that is flagged as "indirect". - -> An instruction's gas cost will roughly align with the number of rows it corresponds to in the SNARK execution trace including rows in the sub-operation table, memory table, chiplet tables, etc. - -> An instruction's gas cost takes into account the costs of associated downstream computations. An instruction that triggers accesses to the public data tree (`SLOAD`/`SSTORE`) incurs a cost that accounts for state access validation in later circuits (public kernel or rollup). A contract call instruction (`CALL`/`STATICCALL`/`DELEGATECALL`) incurs a cost accounting for the nested call's complete execution as well as any work required by the public kernel circuit for this additional call. - -### Halting - -A context's execution can end with a **normal halt** or **exceptional halt**. A halt ends execution within the current context and returns control flow to the calling context. - -#### Normal halting - -A normal halt occurs when the VM encounters an explicit halting instruction ([`RETURN`](./instruction-set#isa-section-return) or [`REVERT`](./instruction-set#isa-section-revert)). Such instructions consume gas normally and optionally initialize some output data before finally halting the current context's execution. - -``` -machineState.l1GasLeft -= instr.l1GasCost -machineState.l2GasLeft -= instr.l2GasCost -machineState.daGasLeft -= instr.daGasCost -results.reverted = instr.opcode == REVERT -results.output = machineState.memory[instr.args.retOffset:instr.args.retOffset+instr.args.retSize] -``` - -> Definitions: `retOffset` and `retSize` here are arguments to the [`RETURN`](./instruction-set/#isa-section-return) and [`REVERT`](./instruction-set#isa-section-revert) instructions. If `retSize` is 0, the context will have no output. Otherwise, these arguments point to a region of memory to output. - -> `results.output` is only relevant when the caller is a contract call itself. In other words, it is only relevant for [nested contract calls](#nested-contract-calls). When an [initial contract call](#initial-contract-calls) (initiated by a public execution request) halts normally, its `results.output` is ignored. - -#### Exceptional halting - -An exceptional halt is not explicitly triggered by an instruction but instead occurs when an exceptional condition is met. - -When an exceptional halt occurs, the context is flagged as consuming all of its allocated gas and is marked as `reverted` with no output data, and then execution within the current context ends. - -``` -machineState.l1GasLeft = 0 -machineState.l2GasLeft = 0 -machineState.daGasLeft = 0 -results.reverted = true -// results.output remains empty -``` - -The AVM's exceptional halting conditions area listed below: - -1. **Insufficient gas** - ``` - assert machineState.l1GasLeft - instr.l1GasCost > 0 - assert machineState.l2GasLeft - instr.l2GasCost > 0 - assert machineState.daGasLeft - instr.l2GasCost > 0 - ``` -1. **Invalid instruction encountered** - ``` - assert environment.bytecode[machineState.pc].opcode <= MAX_AVM_OPCODE - ``` -1. **Jump destination past end of bytecode** - ``` - assert environment.bytecode[machineState.pc].opcode not in {JUMP, JUMPI, INTERNALCALL} - OR instr.args.loc < environment.bytecode.length - ``` -1. **Failed memory tag check** - - Defined per-instruction in the [Instruction Set](./instruction-set) -1. **Maximum memory index ($2^{32}$) exceeded** - ``` - for offset in instr.args.*Offset: - assert offset < 2^32 - ``` -1. **World state modification attempt during a static call** - ``` - assert !environment.isStaticCall - OR environment.bytecode[machineState.pc].opcode not in WS_AS_MODIFYING_OPS - ``` - > Definition: `WS_AS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state or accrued substate. -1. **Maximum contract call depth (1024) exceeded** - ``` - assert environment.contractCallDepth <= 1024 - assert environment.bytecode[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} - OR environment.contractCallDepth < 1024 - ``` -1. **Maximum contract call calls per execution request (1024) exceeded** - ``` - assert worldStateAccessTrace.contractCalls.length <= 1024 - assert environment.bytecode[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} - OR worldStateAccessTrace.contractCalls.length < 1024 - ``` -1. **Maximum internal call depth (1024) exceeded** - ``` - assert machineState.internalCallStack.length <= 1024 - assert environment.bytecode[machineState.pc].opcode != INTERNALCALL - OR environment.contractCallDepth < 1024 - ``` -1. **Maximum world state accesses (1024-per-category) exceeded** - ``` - assert worldStateAccessTrace.publicStorageReads.length <= 1024 - AND worldStateAccessTrace.publicStorageWrites.length <= 1024 - AND worldStateAccessTrace.noteHashChecks.length <= 1024 - AND worldStateAccessTrace.newNoteHashes.length <= 1024 - AND worldStateAccessTrace.nullifierChecks.length <= 1024 - AND worldStateAccessTrace.newNullifiers.length <= 1024 - AND worldStateAccessTrace.l1ToL2MessageReads.length <= 1024 - AND worldStateAccessTrace.archiveChecks.length <= 1024 - - // Storage - assert environment.bytecode[machineState.pc].opcode != SLOAD - OR worldStateAccessTrace.publicStorageReads.length < 1024 - assert environment.bytecode[machineState.pc].opcode != SSTORE - OR worldStateAccessTrace.publicStorageWrites.length < 1024 - - // Note hashes - assert environment.bytecode[machineState.pc].opcode != NOTEHASHEXISTS - OR noteHashChecks.length < 1024 - assert environment.bytecode[machineState.pc].opcode != EMITNOTEHASH - OR newNoteHashes.length < 1024 - - // Nullifiers - assert environment.bytecode[machineState.pc].opcode != NULLIFIEREXISTS - OR nullifierChecks.length < 1024 - assert environment.bytecode[machineState.pc].opcode != EMITNULLIFIER - OR newNullifiers.length < 1024 - - // Read L1 to L2 messages - assert environment.bytecode[machineState.pc].opcode != READL1TOL2MSG - OR worldStateAccessTrace.l1ToL2MessagesReads.length < 1024 - - // Archive tree & Headers - assert environment.bytecode[machineState.pc].opcode != HEADERMEMBER - OR archiveChecks.length < 1024 - ``` -1. **Maximum accrued substate entries (per-category) exceeded** - ``` - assert accruedSubstate.unencryptedLogs.length <= MAX_UNENCRYPTED_LOGS - AND accruedSubstate.sentL2ToL1Messages.length <= MAX_SENT_L2_TO_L1_MESSAGES - - // Unencrypted logs - assert environment.bytecode[machineState.pc].opcode != ULOG - OR unencryptedLogs.length < MAX_UNENCRYPTED_LOGS - - // Sent L2 to L1 messages - assert environment.bytecode[machineState.pc].opcode != SENDL2TOL1MSG - OR sentL2ToL1Messages.length < MAX_SENT_L2_TO_L1_MESSAGES - ``` - > Note that ideally the AVM should limit the _total_ accrued substate entries per-category instead of the entries per-call. - -## Initial contract calls - -An **initial contract call** initializes a new execution context from a public execution request. - -### Context initialization for initial contract calls - -An initial contract call initializes its execution context as follows: -``` -context = AvmContext { - environment = INITIAL_EXECUTION_ENVIRONMENT, - machineState = INITIAL_MACHINE_STATE, - worldState = , - worldStateAccessTrace = INITIAL_WORLD_STATE_ACCESS_TRACE, - accruedSubstate = { [], ... [], }, // all substate vectors empty - results = INITIAL_CONTRACT_CALL_RESULTS, -} -``` - -> Since world state persists between transactions, the latest state is injected into a new AVM context. - -Given a [`PublicCallRequest`](../transactions/tx-object#public-call-request) and its parent [`TxRequest`](../transactions/local-execution#execution-request), these above-listed "`INITIAL_*`" entries are defined as follows: - -``` -INITIAL_EXECUTION_ENVIRONMENT = ExecutionEnvironment { - address: PublicCallRequest.contractAddress, - storageAddress: PublicCallRequest.CallContext.storageContractAddress, - origin: TxRequest.origin, - sender: PublicCallRequest.CallContext.msgSender, - portal: PublicCallRequest.CallContext.portalContractAddress, - feePerL1Gas: TxRequest.feePerL1Gas, - feePerL2Gas: TxRequest.feePerL2Gas, - feePerDaGas: TxRequest.feePerDaGas, - contractCallDepth: 0, - contractCallPointer: 1, - globals: - isStaticCall: PublicCallRequest.CallContext.isStaticCall, - isDelegateCall: PublicCallRequest.CallContext.isDelegateCall, - calldata: PublicCallRequest.args, - bytecode: worldState.contracts[PublicCallRequest.contractAddress], -} - -INITIAL_MACHINE_STATE = MachineState { - l1GasLeft: TxRequest.l1GasLimit, - l2GasLeft: TxRequest.l2GasLimit, - daGasLeft: TxRequest.daGasLimit, - pc: 0, - internalCallStack: [], // initialized as empty - memory: [0, ..., 0], // all 2^32 entries are initialized to zero -} - -INITIAL_WORLD_STATE_ACCESS_TRACE = WorldStateAccessTrace { - accessCounter: 1, - contractCalls: [ // initial contract call is traced - TracedContractCall { - callPointer: nestedContext.environment.callPointer, - address: nestedContext.address, - storageAddress: nestedContext.storageAddress, - counter: 0, - endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts - } - ], - [], ... [], // remaining entries are empty -}, - -INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { - reverted = false, - output = [], // initialized as empty -} -``` - -## Nested contract calls - -To review, a **nested contract call** occurs _during_ AVM execution and is triggered by a contract call instruction ([`CALL`](./instruction-set/#isa-section-call), [`STATICCALL`](./instruction-set/#isa-section-call), or `DELEGATECALL`). It initializes a new execution context (**nested context**) from the current one (the **calling context**) along with the call instruction's arguments. A nested contract call triggers AVM execution in that new context, and returns execution to the calling context upon completion. - -### Context initialization for nested calls - -A nested contract call initializes its execution context as follows: - -``` -nestedContext = AvmContext { - environment: nestedExecutionEnvironment, // defined below - machineState: nestedMachineState, // defined below - worldState: callingContext.worldState, - worldStateAccessTrace: callingContext.worldStateAccessTrace, - accruedSubstate = { [], ... [], }, // all substate vectors empty - results: INITIAL_CONTRACT_CALL_RESULTS, -} -``` - -While some context members are initialized as empty (as they are for an initial contract call), other entries are derived from the calling context or from the contract call instruction's arguments (`instr.args`). - -The world state is forwarded as-is to the nested context. Any updates made to the world state before this contract call instruction was encountered are carried forward into the nested context. - -The environment and machine state for the new context are initialized as shown below: - -``` -// some assignments reused below -isStaticCall = instr.opcode == STATICCALL_OP -isDelegateCall = instr.opcode == DELEGATECALL_OP -contract = callingContext.worldState.contracts[instr.args.addr] -calldataStart = instr.args.argsOffset -calldataEnd = calldataStart + instr.args.argsSize - -nestedExecutionEnvironment = ExecutionEnvironment { - origin: callingContext.origin, - sender: callingContext.address, - address: instr.args.addr, - storageAddress: isDelegateCall ? callingContext.environment.storageAddress : instr.args.addr, - portal: contract.portal, - feePerL1Gas: callingContext.feePerL1Gas, - feePerL2Gas: callingContext.feePerL2Gas, - feePerDaGas: callingContext.feePerDaGas, - contractCallDepth: callingContext.contractCallDepth + 1, - contractCallPointer: callingContext.worldStateAccessTrace.contractCalls.length + 1, - globals: callingContext.globals, - isStaticCall: isStaticCall, - isDelegateCall: isDelegateCall, - calldata: callingContext.memory[calldataStart:calldataEnd], - bytecode: contract.bytecode, -} - -nestedMachineState = MachineState { - l1GasLeft: callingContext.machineState.memory[instr.args.gasOffset], - l2GasLeft: callingContext.machineState.memory[instr.args.gasOffset+1], - daGasLeft: callingContext.machineState.memory[instr.args.gasOffset+2], - pc = 0, - internalCallStack = [], // initialized as empty - memory = [0, ..., 0], // all 2^32 entries are initialized to zero -} -``` -> The nested context's machine state's `*GasLeft` is initialized based on the call instruction's `gasOffset` argument. The caller allocates some amount of L1, L2, and DA gas to the nested call. It does so using the instruction's `gasOffset` argument. In particular, prior to the contract call instruction, the caller populates `M[gasOffset]` with the nested context's initial `l1GasLeft`. Likewise it populates `M[gasOffset+1]` with `l2GasLeft` and `M[gasOffset+2]` with `daGasLeft`. - -> Recall that initial values named as `INITIAL_*` are the same ones used during [context initialization for an initial contract call](#context-initialization-for-initial-contract-calls). - -> `STATICCALL_OP` and `DELEGATECALL_OP` refer to the 8-bit opcode values for the `STATICCALL` and `DELEGATECALL` instructions respectively. - -### Updating the calling context after nested call halts - -A nested context's execution proceeds until it reaches a [halt](#halting). At that point, control returns to the caller, and the calling context is updated based on the nested context and the contract call instruction's transition function. The components of that transition function are defined below. - -The success or failure of the nested call is captured into memory at the offset specified by the call instruction's `successOffset` input: - -``` -context.machineState.memory[instr.args.successOffset] = !nestedContext.results.reverted -``` - -Recall that a contract call is allocated some gas. In particular, the nested call instruction's `gasOffset` input points to an L1, L2, and DA gas allocation for the nested call. As shown in the [section above](#context-initialization-for-nested-calls), a nested call's `machineState.l1GasLeft` is initialized to `callingContext.machineState.memory[instr.args.gasOffset]`. Likewise, `l2GasLeft` is initialized from `gasOffset+1` and `daGasLeft` from `gasOffset+2`. - -As detailed in [the gas section above](#gas-cost-notes-and-examples), every instruction has an associated `instr.l1GasCost`, `instr.l2GasCost`, and `instr.daGasCost`. A nested call instruction's cost is the same as its initial `*GasLeft`. Prior to the nested context's execution, this cost is subtracted from the calling context's remaining gas. - -When a nested context halts, any of its allocated gas that remains unused is refunded to the caller. - -``` -context.l1GasLeft += nestedContext.machineState.l1GasLeft -context.l2GasLeft += nestedContext.machineState.l2GasLeft -context.daGasLeft += nestedContext.machineState.daGasLeft -``` - -If a nested context halts normally with a [`RETURN`](./instruction-set#isa-section-return) or [`REVERT`](./instruction-set#isa-section-revert), it may have some output data (`nestedContext.results.output`). The nested call instruction's `retOffset` and `retSize` arguments specify a region in the calling context's memory to place output data when the nested context halts. - -``` -if instr.args.retSize > 0: - context.memory[instr.args.retOffset:instr.args.retOffset+instr.args.retSize] = nestedContext.results.output -``` - -As long as a nested context has not reverted, its updates to the world state and accrued substate will be absorbed into the calling context. - -``` -if !nestedContext.results.reverted AND instr.opcode != STATICCALL_OP: - context.worldState = nestedContext.worldState - context.accruedSubstate.append(nestedContext.accruedSubstate) -``` - -Before the nested call's world state access trace is absorbed into the calling context, if the nested context reverted, all accesses made during the nested call (or any further nested calls) must be flagged with an **end-lifetime**, signifying that the access is only relevant up until that point. Thus, the `endLifetime` for all accesses made during the nested call (or any deeper nested calls) is updated according to the nested call's final `accessCounter`. -``` -if nestedContext.results.reverted: - // process all traces (this is shorthand) - for trace in nestedContext.worldStateAccessTrace: - for access in trace: - if access.callPointer >= nestedContext.environment.callPointer: - // don't override end-lifetime already set by a deeper nested call - if access.endLifetime == 0: - access.endLifetime = nestedContext.worldStateAccessTrace.accessCounter -``` - -> Note that `endLifetime` of 0 signifies "no end". - -> Note this also updates the nested call's entry in the `contractCalls` trace. - -Regardless of whether a nested context has reverted, its [world state access trace](./state#world-state-access-trace) updates are absorbed into the calling context. -``` -context.worldStateAccessTrace = nestedContext.worldStateAccessTrace -``` - -> Reminder: a nested call cannot make updates to the world state or accrued substate if it is a [`STATICCALL`](./instruction-set/#isa-section-staticcall). diff --git a/yellow-paper/docs/public-vm/context.md b/yellow-paper/docs/public-vm/context.md new file mode 100644 index 00000000000..6f823c876e9 --- /dev/null +++ b/yellow-paper/docs/public-vm/context.md @@ -0,0 +1,119 @@ +# Execution Context + +:::note REMINDER +Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). +::: + +An **execution context** includes the information and state relevant to a contract call's execution. When a contract call is made, an execution context is initialized as specified in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](./nested-calls) sections. + +#### _AvmContext_ +| Field | Type | +| --- | --- | +| environment | `ExecutionEnvironment` | +| [machineState](./state#machine-state) | `MachineState` | +| [worldState](./state#avm-world-state) | `AvmWorldState` | +| [worldStateAccessTrace](./state#world-state-access-trace) | `WorldStateAccessTrace` | +| [accruedSubstate](./state#accrued-substate) | `AccruedSubstate` | +| results | `ContractCallResults` | + +## Execution Environment + +A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it fully specifies the execution environment. This is expanded on in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](./nested-calls) sections. + +### _ExecutionEnvironment_ +| Field | Type | Description | +| --- | --- | --- | +| address | `AztecAddress` | | +| storageAddress | `AztecAddress` | | +| origin | `AztecAddress` | | +| sender | `AztecAddress` | | +| portal | `EthAddress` | | +| feePerL1Gas | `field` | | +| feePerL2Gas | `field` | | +| feePerDaGas | `field` | | +| contractCallDepth | `field` | Depth of the current call (how many nested calls deep is it). | +| contractCallPointer | `field` | Uniquely identifies each contract call processed by an AVM session. An initial call is assigned pointer value of 1 (expanded on in the AVM circuit section's ["Call Pointer"](./avm-circuit#call-pointer) subsection). | +| globals | `PublicGlobalVariables` | | +| isStaticCall | `boolean` | | +| isDelegateCall | `boolean` | | +| calldata | `[field; ]` | | + +## Contract Call Results + +When a contract call halts, it sets the context's **contract call results** to communicate results to the caller. + +### _ContractCallResults_ +| Field | Type | Description | +| --- | --- | --- | +| reverted | `boolean` | | +| output | `[field; ]` | | + +## Initial contract calls + +### Context initialization for initial contract calls + +An **initial contract call** initializes a new execution context from a public execution request. + +An initial contract call initializes its execution context as follows: +``` +context = AvmContext { + environment = INITIAL_EXECUTION_ENVIRONMENT, + machineState = INITIAL_MACHINE_STATE, + worldState = , + worldStateAccessTrace = INITIAL_WORLD_STATE_ACCESS_TRACE, + accruedSubstate = { [], ... [], }, // all substate vectors empty + results = INITIAL_CONTRACT_CALL_RESULTS, +} +``` + +> Since world state persists between transactions, the latest state is injected into a new AVM context. + +Given a [`PublicCallRequest`](../transactions/tx-object#public-call-request) and its parent [`TxRequest`](../transactions/local-execution#execution-request), these above-listed "`INITIAL_*`" entries are defined as follows: + +``` +INITIAL_EXECUTION_ENVIRONMENT = ExecutionEnvironment { + address: PublicCallRequest.contractAddress, + storageAddress: PublicCallRequest.CallContext.storageContractAddress, + origin: TxRequest.origin, + sender: PublicCallRequest.CallContext.msgSender, + portal: PublicCallRequest.CallContext.portalContractAddress, + feePerL1Gas: TxRequest.feePerL1Gas, + feePerL2Gas: TxRequest.feePerL2Gas, + feePerDaGas: TxRequest.feePerDaGas, + contractCallDepth: 0, + contractCallPointer: 1, + globals: + isStaticCall: PublicCallRequest.CallContext.isStaticCall, + isDelegateCall: PublicCallRequest.CallContext.isDelegateCall, + calldata: PublicCallRequest.args, + bytecode: worldState.contracts[PublicCallRequest.contractAddress], +} + +INITIAL_MACHINE_STATE = MachineState { + l1GasLeft: TxRequest.l1GasLimit, + l2GasLeft: TxRequest.l2GasLimit, + daGasLeft: TxRequest.daGasLimit, + pc: 0, + internalCallStack: [], // initialized as empty + memory: [0, ..., 0], // all 2^32 entries are initialized to zero +} + +INITIAL_WORLD_STATE_ACCESS_TRACE = WorldStateAccessTrace { + accessCounter: 1, + contractCalls: [ // initial contract call is traced + TracedContractCall { + callPointer: nestedContext.environment.callPointer, + address: nestedContext.address, + storageAddress: nestedContext.storageAddress, + counter: 0, + endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts + } + ], + [], ... [], // remaining entries are empty +}, + +INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { + reverted = false, + output = [], // initialized as empty +} +``` diff --git a/yellow-paper/docs/public-vm/execution.md b/yellow-paper/docs/public-vm/execution.md new file mode 100644 index 00000000000..73d0b360a71 --- /dev/null +++ b/yellow-paper/docs/public-vm/execution.md @@ -0,0 +1,190 @@ +# Execution, Gas, Halting + +Once an execution context has been initialized for a contract call, the [machine state's](./state#machine-state) program counter determines which instruction the AVM executes. For any contract call, the program counter starts at zero, and so instruction execution begins with the very first entry in a contract's bytecode. + +## Program Counter and Control Flow + +The program counter (`machineState.pc`) determines which instruction the AVM executes next (`instr = environment.bytecode[pc]`). Each instruction's execution updates the program counter in some way, which allows the AVM to progress to the next instruction at each step. + +Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./instruction-set#isa-section-jump), [`JUMPI`](./instruction-set#isa-section-jumpi), [`INTERNALCALL`](./instruction-set#isa-section-internalcall)) modify the program counter based on arguments. + +The `INTERNALCALL` instruction pushes `machineState.pc+1` to `machineState.internalCallStack` and then updates `pc` to the instruction's destination argument (`instr.args.loc`). The `INTERNALRETURN` instruction pops a destination from `machineState.internalCallStack` and assigns the result to `pc`. + +> An instruction will never assign program counter a value from memory (`machineState.memory`). A `JUMP`, `JUMPI`, or `INTERNALCALL` instruction's destination is a constant from the program bytecode. This property allows for easier static program analysis. + +## Gas limits and tracking +> See ["Gas and Fees"](../gas-and-fees) for a deeper dive into Aztec's gas model and for definitions of each type of gas. + +Each instruction has an associated `l1GasCost`, `l2GasCost`, and `daGasCost`. Before an instruction is executed, the VM enforces that there is sufficient gas remaining via the following assertions: +``` +assert machineState.l1GasLeft - instr.l1GasCost > 0 +assert machineState.l2GasLeft - instr.l2GasCost > 0 +assert machineState.daGasLeft - instr.daGasCost > 0 +``` + +> Many instructions (like arithmetic operations) have 0 `l1GasCost` and `daGasCost`. Instructions only incur an L1 or DA cost if they modify the [world state](./state#avm-world-state) or [accrued substate](./state#accrued-substate). + +If these assertions pass, the machine state's gas left is decreased prior to the instruction's core execution: + +``` +machineState.l1GasLeft -= instr.l1GasCost +machineState.l2GasLeft -= instr.l2GasCost +machineState.daGasLeft -= instr.daGasCost +``` + +If either of these assertions _fail_ for an instruction, this triggers an exceptional halt. The gas left is set to 0 and execution reverts. + +``` +machineState.l1GasLeft = 0 +machineState.l2GasLeft = 0 +machineState.daGasLeft = 0 +``` + +> Reverting and exceptional halts are covered in more detail in the ["Halting" section](#halting). + +## Gas cost notes and examples + +An instruction's gas cost is meant to reflect the computational cost of generating a proof of its correct execution. For some instructions, this computational cost changes based on inputs. Here are some examples and important notes: +- [`JUMP`](./instruction-set/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l1GasCost`, `l2GasCost`, and `daGasCost`. +- The [`SET`](./instruction-set/#isa-section-set) instruction operates on a different sized constant (based on its `dstTag`). Therefore, this instruction's gas cost increases with the size of its input. +- Instructions that operate on a data range of a specified "size" scale in cost with that size. An example of this is the [`CALLDATACOPY`](./instruction-set/#isa-section-calldatacopy) argument which copies `copySize` words from `environment.calldata` to `machineState.memory`. +- The [`CALL`](./instruction-set/#isa-section-call)/[`STATICCALL`](./instruction-set/#isa-section-call)/`DELEGATECALL` instruction's gas cost is determined by its `*Gas` arguments, but any gas unused by the nested contract call's execution is refunded after its completion ([more on this later](./nested-calls#updating-the-calling-context-after-nested-call-halts)). +- An instruction with "offset" arguments (like [`ADD`](./instruction-set/#isa-section-add) and many others), has increased cost for each offset argument that is flagged as "indirect". + +> An instruction's gas cost will roughly align with the number of rows it corresponds to in the SNARK execution trace including rows in the sub-operation table, memory table, chiplet tables, etc. + +> An instruction's gas cost takes into account the costs of associated downstream computations. An instruction that triggers accesses to the public data tree (`SLOAD`/`SSTORE`) incurs a cost that accounts for state access validation in later circuits (public kernel or rollup). A contract call instruction (`CALL`/`STATICCALL`/`DELEGATECALL`) incurs a cost accounting for the nested call's complete execution as well as any work required by the public kernel circuit for this additional call. + +## Halting + +A context's execution can end with a **normal halt** or **exceptional halt**. A halt ends execution within the current context and returns control flow to the calling context. + +### Normal halting + +A normal halt occurs when the VM encounters an explicit halting instruction ([`RETURN`](./instruction-set#isa-section-return) or [`REVERT`](./instruction-set#isa-section-revert)). Such instructions consume gas normally and optionally initialize some output data before finally halting the current context's execution. + +``` +machineState.l1GasLeft -= instr.l1GasCost +machineState.l2GasLeft -= instr.l2GasCost +machineState.daGasLeft -= instr.daGasCost +results.reverted = instr.opcode == REVERT +results.output = machineState.memory[instr.args.retOffset:instr.args.retOffset+instr.args.retSize] +``` + +> Definitions: `retOffset` and `retSize` here are arguments to the [`RETURN`](./instruction-set/#isa-section-return) and [`REVERT`](./instruction-set#isa-section-revert) instructions. If `retSize` is 0, the context will have no output. Otherwise, these arguments point to a region of memory to output. + +> `results.output` is only relevant when the caller is a contract call itself. In other words, it is only relevant for [nested contract calls](./nested-calls). When an [initial contract call](./context#initial-contract-calls) (initiated by a public execution request) halts normally, its `results.output` is ignored. + +### Exceptional halting + +An exceptional halt is not explicitly triggered by an instruction but instead occurs when an exceptional condition is met. + +When an exceptional halt occurs, the context is flagged as consuming all of its allocated gas and is marked as `reverted` with no output data, and then execution within the current context ends. + +``` +machineState.l1GasLeft = 0 +machineState.l2GasLeft = 0 +machineState.daGasLeft = 0 +results.reverted = true +// results.output remains empty +``` + +The AVM's exceptional halting conditions area listed below: + +1. **Insufficient gas** + ``` + assert machineState.l1GasLeft - instr.l1GasCost > 0 + assert machineState.l2GasLeft - instr.l2GasCost > 0 + assert machineState.daGasLeft - instr.l2GasCost > 0 + ``` +1. **Invalid instruction encountered** + ``` + assert environment.bytecode[machineState.pc].opcode <= MAX_AVM_OPCODE + ``` +1. **Jump destination past end of bytecode** + ``` + assert environment.bytecode[machineState.pc].opcode not in {JUMP, JUMPI, INTERNALCALL} + OR instr.args.loc < environment.bytecode.length + ``` +1. **Failed memory tag check** + - Defined per-instruction in the [Instruction Set](./instruction-set) +1. **Maximum memory index ($2^{32}$) exceeded** + ``` + for offset in instr.args.*Offset: + assert offset < 2^32 + ``` +1. **World state modification attempt during a static call** + ``` + assert !environment.isStaticCall + OR environment.bytecode[machineState.pc].opcode not in WS_AS_MODIFYING_OPS + ``` + > Definition: `WS_AS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state or accrued substate. +1. **Maximum contract call depth (1024) exceeded** + ``` + assert environment.contractCallDepth <= 1024 + assert environment.bytecode[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} + OR environment.contractCallDepth < 1024 + ``` +1. **Maximum contract call calls per execution request (1024) exceeded** + ``` + assert worldStateAccessTrace.contractCalls.length <= 1024 + assert environment.bytecode[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} + OR worldStateAccessTrace.contractCalls.length < 1024 + ``` +1. **Maximum internal call depth (1024) exceeded** + ``` + assert machineState.internalCallStack.length <= 1024 + assert environment.bytecode[machineState.pc].opcode != INTERNALCALL + OR environment.contractCallDepth < 1024 + ``` +1. **Maximum world state accesses (1024-per-category) exceeded** + ``` + assert worldStateAccessTrace.publicStorageReads.length <= 1024 + AND worldStateAccessTrace.publicStorageWrites.length <= 1024 + AND worldStateAccessTrace.noteHashChecks.length <= 1024 + AND worldStateAccessTrace.newNoteHashes.length <= 1024 + AND worldStateAccessTrace.nullifierChecks.length <= 1024 + AND worldStateAccessTrace.newNullifiers.length <= 1024 + AND worldStateAccessTrace.l1ToL2MessageReads.length <= 1024 + AND worldStateAccessTrace.archiveChecks.length <= 1024 + + // Storage + assert environment.bytecode[machineState.pc].opcode != SLOAD + OR worldStateAccessTrace.publicStorageReads.length < 1024 + assert environment.bytecode[machineState.pc].opcode != SSTORE + OR worldStateAccessTrace.publicStorageWrites.length < 1024 + + // Note hashes + assert environment.bytecode[machineState.pc].opcode != NOTEHASHEXISTS + OR noteHashChecks.length < 1024 + assert environment.bytecode[machineState.pc].opcode != EMITNOTEHASH + OR newNoteHashes.length < 1024 + + // Nullifiers + assert environment.bytecode[machineState.pc].opcode != NULLIFIEREXISTS + OR nullifierChecks.length < 1024 + assert environment.bytecode[machineState.pc].opcode != EMITNULLIFIER + OR newNullifiers.length < 1024 + + // Read L1 to L2 messages + assert environment.bytecode[machineState.pc].opcode != READL1TOL2MSG + OR worldStateAccessTrace.l1ToL2MessagesReads.length < 1024 + + // Archive tree & Headers + assert environment.bytecode[machineState.pc].opcode != HEADERMEMBER + OR archiveChecks.length < 1024 + ``` +1. **Maximum accrued substate entries (per-category) exceeded** + ``` + assert accruedSubstate.unencryptedLogs.length <= MAX_UNENCRYPTED_LOGS + AND accruedSubstate.sentL2ToL1Messages.length <= MAX_SENT_L2_TO_L1_MESSAGES + + // Unencrypted logs + assert environment.bytecode[machineState.pc].opcode != ULOG + OR unencryptedLogs.length < MAX_UNENCRYPTED_LOGS + + // Sent L2 to L1 messages + assert environment.bytecode[machineState.pc].opcode != SENDL2TOL1MSG + OR sentL2ToL1Messages.length < MAX_SENT_L2_TO_L1_MESSAGES + ``` + > Note that ideally the AVM should limit the _total_ accrued substate entries per-category instead of the entries per-call. diff --git a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx index a18b63db11b..3c941a3cc37 100644 --- a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx +++ b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx @@ -1687,7 +1687,7 @@ Call into another contract execution resumes in the current/calling context. A non-existent contract or one with no code will return success. The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + explained further in the ["Nested contract calls"](./nested-calls) section, which details nested context derivation, gas cost and refunds, handling a call's results, and world state access tracing. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` @@ -1727,7 +1727,7 @@ Call into another contract, disallowing World State and Accrued Substate modific - **Details**: Same as `CALL`, but disallows World State and Accrued Substate modifications. The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + explained further in the ["Nested contract calls"](./nested-calls) section, which details nested context derivation, gas cost and refunds, handling a call's results, and world state access tracing. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` @@ -1768,7 +1768,7 @@ Call into another contract, but keep the caller's `sender` and `storageAddress` - **Details**: Same as `CALL`, but `sender` and `storageAddress` remains the same in the nested call as they were in the caller. The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + explained further in the ["Nested contract calls"](./nested-calls) section, which details nested context derivation, gas cost and refunds, handling a call's results, and world state access tracing. - **Tag checks**: `T[gasOffset] == T[gasOffset+1] == T[gasOffset+2] == u32` @@ -1797,7 +1797,7 @@ Halt execution within this context (without revert), optionally returning some d {`context.contractCallResults.output = M[retOffset:retOffset+retSize] halt`} -- **Details**: Return control flow to the calling context/contract. Caller will accept World State and Accrued Substate modifications. See ["Halting"](./avm#halting) to learn more. See ["Nested contract calls"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts. +- **Details**: Return control flow to the calling context/contract. Caller will accept World State and Accrued Substate modifications. See ["Halting"](./execution#halting) to learn more. See ["Nested contract calls"](./nested-calls) to see how the caller updates its context after the nested call halts. - **Bit-size**: 88 [![](./images/bit-formats/RETURN.png)](./images/bit-formats/RETURN.png) @@ -1820,7 +1820,7 @@ Halt execution within this context as `reverted`, optionally returning some data context.contractCallResults.reverted = true halt`} -- **Details**: Return control flow to the calling context/contract. Caller will reject World State and Accrued Substate modifications. See ["Halting"](./avm#halting) to learn more. See ["Nested contract calls"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts. +- **Details**: Return control flow to the calling context/contract. Caller will reject World State and Accrued Substate modifications. See ["Halting"](./execution#halting) to learn more. See ["Nested contract calls"](./nested-calls) to see how the caller updates its context after the nested call halts. - **Bit-size**: 88 [![](./images/bit-formats/REVERT.png)](./images/bit-formats/REVERT.png) diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md new file mode 100644 index 00000000000..9ca789df8e5 --- /dev/null +++ b/yellow-paper/docs/public-vm/intro.md @@ -0,0 +1,43 @@ +j Aztec Virtual Machine + +:::note reference +Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). +::: + +## Introduction + +An Aztec transaction may include one or more **public execution requests**. A public execution request is a request to execute a specified contract's public bytecode given some arguments. Execution of a contract's public bytecode is performed by the **Aztec Virtual Machine (AVM)**. + +> A public execution request may originate from a public call enqueued by a transaction's private segment ([`enqueuedPublicFunctionCalls`](../calls/enqueued-calls.md)), or from a public [fee preparation](../gas-and-fees#fee-preparation) or [fee distribution](../gas-and-fees#fee-distribution) call. + +In order to execute public contract bytecode, the AVM requires some context. An [**execution context**](./context) contains all information necessary to initiate AVM execution, including the relevant contract's bytecode and all state maintained by the AVM. A **contract call** initializes an execution context and triggers AVM execution within that context. + +Instruction-by-instruction, the AVM [executes](./execution) the bytecode specified in its context. An **instruction** is a bytecode entry that, when executed, modifies the AVM's execution context (in particular its [state](./state)) according to the instruction's definition in the ["AVM Instruction Set"](./instruction-set). Execution within a context ends when the AVM encounters a [**halt**](./execution#halting). + +During execution, additional contract calls may be made. While an [**initial contract call**](./context#initial-contract-calls) initializes a new execution context directly from a public execution request, a [**nested contract call**](./nested-calls) occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-call), or `DELEGATECALL`). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. + +A **caller** is a contract call's initiator. The caller of an initial contract call is an Aztec sequencer. The caller of a nested contract call is the AVM itself executing in the calling context. + +## High-level VM Sections + +These sections are meant to provide a high-level definition of the Aztec Virtual Machine as opposed to a specification of its SNARK implementation. The document therefore mostly omits SNARK or circuit-centric verbiage except when particularly relevant to the high-level architecture. + +- [**Public contract bytecode**](#public-contract-bytecode) (aka AVM bytecode) +- [**AVM state**](./state): the state maintained by the AVM +- [**AVM memory model**](./memory-model): the AVM's type-tagged memory model +- [**Execution context**](./context): the AVM's execution context and its initialization for initial contract calls +- [**Execution**](#execution): control flow, gas tracking, normal halting, and exceptional halting +- [**Nested contract calls**](./nested-calls): the initiation of a contract call from an instruction as well as the processing of nested execution results, gas refunds, and state reverts +- [**AVM Instruction Set**](./instruction-set): the list of all instructions supported by the AVM + +## VM Circuit Sections + +- **[AVM Circuit](./avm-circuit)** + +## Public contract bytecode + +A contract's public bytecode is a series of execution instructions for the AVM. Refer to the ["AVM Instruction Set"](./instruction-set) for the details of all supported instructions along with how they modify AVM state. + +The entirety of a contract's public code is represented as a single block of bytecode with a maximum of `MAX_PUBLIC_INSTRUCTIONS_PER_CONTRACT` ($2^{15} = 32768$) instructions. The mechanism used to distinguish between different "functions" in an AVM bytecode program is left as a higher-level abstraction (_e.g._ similar to Solidity's concept of a function selector). + +> See the [Bytecode Validation Circuit](./bytecode-validation-circuit) to see how a contract's bytecode can be validated and committed to. diff --git a/yellow-paper/sidebars.js b/yellow-paper/sidebars.js index f2571444577..9340636b477 100644 --- a/yellow-paper/sidebars.js +++ b/yellow-paper/sidebars.js @@ -185,13 +185,14 @@ const sidebars = { ], }, { - label: "Public VM", + label: "Aztec (Public) VM", type: "category", - link: { type: "doc", id: "public-vm/avm" }, + link: { type: "doc", id: "public-vm/intro" }, items: [ - "public-vm/introduction", + "public-vm/intro", "public-vm/state", "public-vm/memory-model", + "public-vm/context", "public-vm/execution", "public-vm/nested-calls", "public-vm/instruction-set", diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 368c3ca0bac..896e248c11f 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -23,7 +23,7 @@ const CALL_INSTRUCTION_ARGS = [ ]; const CALL_INSTRUCTION_DETAILS = ` The expression's syntax above is shorthand. This instruction is - explained further in the ["Nested contract calls"](./avm#nested-contract-calls) + explained further in the ["Nested contract calls"](./nested-calls) section, which details nested context derivation, gas cost and refunds, handling a call's results, and world state access tracing.`; @@ -1201,7 +1201,7 @@ context.contractCallResults.output = M[retOffset:retOffset+retSize] halt `, "Summary": "Halt execution within this context (without revert), optionally returning some data", - "Details": "Return control flow to the calling context/contract. Caller will accept World State and Accrued Substate modifications. See [\"Halting\"](./avm#halting) to learn more. See [\"Nested contract calls\"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts.", + "Details": "Return control flow to the calling context/contract. Caller will accept World State and Accrued Substate modifications. See [\"Halting\"](./execution#halting) to learn more. See [\"Nested contract calls\"](./nested-calls) to see how the caller updates its context after the nested call halts.", "Tag checks": "", "Tag updates": "", }, @@ -1222,7 +1222,7 @@ context.contractCallResults.reverted = true halt `, "Summary": "Halt execution within this context as `reverted`, optionally returning some data", - "Details": "Return control flow to the calling context/contract. Caller will reject World State and Accrued Substate modifications. See [\"Halting\"](./avm#halting) to learn more. See [\"Nested contract calls\"](./avm#nested-contract-calls) to see how the caller updates its context after the nested call halts.", + "Details": "Return control flow to the calling context/contract. Caller will reject World State and Accrued Substate modifications. See [\"Halting\"](./execution#halting) to learn more. See [\"Nested contract calls\"](./nested-calls) to see how the caller updates its context after the nested call halts.", "Tag checks": "", "Tag updates": "", }, From e003717c293a41b6880a7516685c8406f2af5c56 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 22:32:32 +0000 Subject: [PATCH 10/21] cleanup --- .../docs/contract-deployment/instances.md | 2 +- .../docs/public-vm/_nested-context.md | 45 +++++++++++++++++ .../public-vm/{context.md => context.mdx} | 15 ++++-- yellow-paper/docs/public-vm/execution.md | 2 +- yellow-paper/docs/public-vm/intro.md | 8 ++- yellow-paper/docs/public-vm/memory-model.md | 2 +- .../{nested-calls.md => nested-calls.mdx} | 50 ++----------------- 7 files changed, 66 insertions(+), 58 deletions(-) create mode 100644 yellow-paper/docs/public-vm/_nested-context.md rename yellow-paper/docs/public-vm/{context.md => context.mdx} (90%) rename yellow-paper/docs/public-vm/{nested-calls.md => nested-calls.mdx} (71%) diff --git a/yellow-paper/docs/contract-deployment/instances.md b/yellow-paper/docs/contract-deployment/instances.md index eb31836761f..1f76a5c2827 100644 --- a/yellow-paper/docs/contract-deployment/instances.md +++ b/yellow-paper/docs/contract-deployment/instances.md @@ -145,7 +145,7 @@ Specific to private functions: Specific to public functions: -- The bytecode loaded by the [AVM](../public-vm/avm.md) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit.md). +- The bytecode loaded by the [AVM](../public-vm) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit.md). - The contract Deployment Nullifier has been emitted, or prove that it hasn't, in which case the transaction is expected to revert. This check is done via a merkle (non-)membership proof of the Deployment Nullifier. Note that a public function should be callable in the same transaction in which its contract Deployment Nullifier was emitted. Note that, since constructors are handled at the application level, the kernel circuit is not required to check the Initialization Nullifier before executing code. diff --git a/yellow-paper/docs/public-vm/_nested-context.md b/yellow-paper/docs/public-vm/_nested-context.md new file mode 100644 index 00000000000..e3b3117fb31 --- /dev/null +++ b/yellow-paper/docs/public-vm/_nested-context.md @@ -0,0 +1,45 @@ +A nested contract call's execution environment and machine state are derived from the caller's context and the call instruction's arguments. + +```jsx +// contract being called into +contract = callingContext.worldState.contracts[M[addrOffset]] + +nestedExecutionEnvironment = ExecutionEnvironment { + origin: context.origin, + sender: isDelegateCall ? context.sender : context.address, + address: M[addrOffset], + storageAddress: isDelegateCall ? context.storageAddress : M[addrOffset], + portal: contract.portal, + feePerL1Gas: context.environment.feePerL1Gas, + feePerL2Gas: context.environment.feePerL2Gas, + feePerDaGas: context.environment.feePerDaGas, + contractCallDepth: context.contractCallDepth + 1, + contractCallPointer: context.worldStateAccessTrace.contractCalls.length + 1, + globals: context.globals, + isStaticCall: isStaticCall, + isDelegateCall: isDelegateCall, + calldata: context.memory[M[argsOffset]:M[argsOffset]+argsSize], + bytecode: contract.bytecode, +} + +nestedMachineState = MachineState { + l1GasLeft: context.machineState.memory[M[gasOffset]], + l2GasLeft: context.machineState.memory[M[gasOffset+1]], + daGasLeft: context.machineState.memory[M[gasOffset+2]], + pc = 0, + internalCallStack = [], // initialized as empty + memory = [0, ..., 0], // all 2^32 entries are initialized to zero +} +``` + +The nested call's execution context is then initialized. +```jsx +nestedContext = AvmContext { + environment: nestedExecutionEnvironment, + machineState: nestedMachineState, + worldState: context.worldState, + worldStateAccessTrace: context.worldStateAccessTrace, + accruedSubstate: { [], ... [], }, // all empty + results: {reverted: false, output: []}, +} +``` diff --git a/yellow-paper/docs/public-vm/context.md b/yellow-paper/docs/public-vm/context.mdx similarity index 90% rename from yellow-paper/docs/public-vm/context.md rename to yellow-paper/docs/public-vm/context.mdx index 6f823c876e9..44ed4c5ff44 100644 --- a/yellow-paper/docs/public-vm/context.md +++ b/yellow-paper/docs/public-vm/context.mdx @@ -4,7 +4,7 @@ Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). ::: -An **execution context** includes the information and state relevant to a contract call's execution. When a contract call is made, an execution context is initialized as specified in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](./nested-calls) sections. +An **execution context** contains the information and state relevant to a contract call's execution. When a contract call is made, an execution context is initialized before the contract code's execution begins. Context initialization is expanded on in ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls). #### _AvmContext_ | Field | Type | @@ -18,7 +18,7 @@ An **execution context** includes the information and state relevant to a contra ## Execution Environment -A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it fully specifies the execution environment. This is expanded on in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](./nested-calls) sections. +A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it fully specifies the execution environment. This is expanded on in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls) sections. ### _ExecutionEnvironment_ | Field | Type | Description | @@ -50,11 +50,8 @@ When a contract call halts, it sets the context's **contract call results** to c ## Initial contract calls -### Context initialization for initial contract calls - An **initial contract call** initializes a new execution context from a public execution request. -An initial contract call initializes its execution context as follows: ``` context = AvmContext { environment = INITIAL_EXECUTION_ENVIRONMENT, @@ -117,3 +114,11 @@ INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { output = [], // initialized as empty } ``` + +## Nested contract calls + +> See the dedicated ("Nested Contract Calls")[./nested-calls] page for a detailed explanation of nested contract calls. + +import NestedContext from "./_nested-context.md"; + + \ No newline at end of file diff --git a/yellow-paper/docs/public-vm/execution.md b/yellow-paper/docs/public-vm/execution.md index 73d0b360a71..1ca63e09961 100644 --- a/yellow-paper/docs/public-vm/execution.md +++ b/yellow-paper/docs/public-vm/execution.md @@ -48,7 +48,7 @@ An instruction's gas cost is meant to reflect the computational cost of generati - [`JUMP`](./instruction-set/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l1GasCost`, `l2GasCost`, and `daGasCost`. - The [`SET`](./instruction-set/#isa-section-set) instruction operates on a different sized constant (based on its `dstTag`). Therefore, this instruction's gas cost increases with the size of its input. - Instructions that operate on a data range of a specified "size" scale in cost with that size. An example of this is the [`CALLDATACOPY`](./instruction-set/#isa-section-calldatacopy) argument which copies `copySize` words from `environment.calldata` to `machineState.memory`. -- The [`CALL`](./instruction-set/#isa-section-call)/[`STATICCALL`](./instruction-set/#isa-section-call)/`DELEGATECALL` instruction's gas cost is determined by its `*Gas` arguments, but any gas unused by the nested contract call's execution is refunded after its completion ([more on this later](./nested-calls#updating-the-calling-context-after-nested-call-halts)). +- The [`CALL`](./instruction-set#isa-section-call)/[`STATICCALL`](./instruction-set#isa-section-staticcall)/[`DELEGATECALL`](./instruction-set#isa-section-delegatecall) instruction's gas cost is determined by its `*Gas` arguments, but any gas unused by the nested contract call's execution is refunded after its completion ([more on this later](./nested-calls#updating-the-calling-context-after-nested-call-halts)). - An instruction with "offset" arguments (like [`ADD`](./instruction-set/#isa-section-add) and many others), has increased cost for each offset argument that is flagged as "indirect". > An instruction's gas cost will roughly align with the number of rows it corresponds to in the SNARK execution trace including rows in the sub-operation table, memory table, chiplet tables, etc. diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md index 9ca789df8e5..6cd6a26cbbc 100644 --- a/yellow-paper/docs/public-vm/intro.md +++ b/yellow-paper/docs/public-vm/intro.md @@ -1,11 +1,9 @@ -j Aztec Virtual Machine +# Introduction :::note reference -Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). +Many terms and definitions are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). ::: -## Introduction - An Aztec transaction may include one or more **public execution requests**. A public execution request is a request to execute a specified contract's public bytecode given some arguments. Execution of a contract's public bytecode is performed by the **Aztec Virtual Machine (AVM)**. > A public execution request may originate from a public call enqueued by a transaction's private segment ([`enqueuedPublicFunctionCalls`](../calls/enqueued-calls.md)), or from a public [fee preparation](../gas-and-fees#fee-preparation) or [fee distribution](../gas-and-fees#fee-distribution) call. @@ -14,7 +12,7 @@ In order to execute public contract bytecode, the AVM requires some context. An Instruction-by-instruction, the AVM [executes](./execution) the bytecode specified in its context. An **instruction** is a bytecode entry that, when executed, modifies the AVM's execution context (in particular its [state](./state)) according to the instruction's definition in the ["AVM Instruction Set"](./instruction-set). Execution within a context ends when the AVM encounters a [**halt**](./execution#halting). -During execution, additional contract calls may be made. While an [**initial contract call**](./context#initial-contract-calls) initializes a new execution context directly from a public execution request, a [**nested contract call**](./nested-calls) occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-call), or `DELEGATECALL`). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. +During execution, additional contract calls may be made. While an [**initial contract call**](./context#initial-contract-calls) initializes a new execution context directly from a public execution request, a [**nested contract call**](./nested-calls) occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), or [`DELEGATECALL`](./instruction-set#isa-section-delegatecall)). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. A **caller** is a contract call's initiator. The caller of an initial contract call is an Aztec sequencer. The caller of a nested contract call is the AVM itself executing in the calling context. diff --git a/yellow-paper/docs/public-vm/memory-model.md b/yellow-paper/docs/public-vm/memory-model.md index 28f65c03de2..444ce2aab82 100644 --- a/yellow-paper/docs/public-vm/memory-model.md +++ b/yellow-paper/docs/public-vm/memory-model.md @@ -1,4 +1,4 @@ -# Memory State Model +# Memory Model This section describes the AVM memory model, and in particular specifies "internal" VM abstractions that can be mapped to the VM's circuit architecture. diff --git a/yellow-paper/docs/public-vm/nested-calls.md b/yellow-paper/docs/public-vm/nested-calls.mdx similarity index 71% rename from yellow-paper/docs/public-vm/nested-calls.md rename to yellow-paper/docs/public-vm/nested-calls.mdx index 8685775f99b..6d84ebfef71 100644 --- a/yellow-paper/docs/public-vm/nested-calls.md +++ b/yellow-paper/docs/public-vm/nested-calls.mdx @@ -1,4 +1,6 @@ -# Nested contract calls +# Nested Contract Calls + +A **nested contract call** occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), or [`DELEGATECALL`](./instruction-set#isa-section-delegatecall)). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. ## Nested Call Instructions @@ -25,52 +27,10 @@ context.worldStateAccessTrace.contractCalls.append( ## Context initialization for nested calls -The contract being called into is referenced as `contract` in the following definitions: -```jsx -contract = callingContext.worldState.contracts[M[addrOffset]] -``` - -A nested contract call's execution environment and machine state are derived from the caller's context and the call instruction's arguments: +import NestedContext from "./_nested-context.md"; -```jsx -nestedExecutionEnvironment = ExecutionEnvironment { - origin: context.origin, - sender: isDelegateCall ? context.sender : context.address, - address: M[addrOffset], - storageAddress: isDelegateCall ? context.storageAddress : M[addrOffset], - portal: contract.portal, - feePerL1Gas: context.environment.feePerL1Gas, - feePerL2Gas: context.environment.feePerL2Gas, - feePerDaGas: context.environment.feePerDaGas, - contractCallDepth: context.contractCallDepth + 1, - contractCallPointer: context.worldStateAccessTrace.contractCalls.length + 1, - globals: context.globals, - isStaticCall: isStaticCall, - isDelegateCall: isDelegateCall, - calldata: context.memory[M[argsOffset]:M[argsOffset]+argsSize], - bytecode: contract.bytecode, -} -nestedMachineState = MachineState { - l1GasLeft: context.machineState.memory[M[gasOffset]], - l2GasLeft: context.machineState.memory[M[gasOffset+1]], - daGasLeft: context.machineState.memory[M[gasOffset+2]], - pc = 0, - internalCallStack = [], // initialized as empty - memory = [0, ..., 0], // all 2^32 entries are initialized to zero -} -``` + -The nested call's execution context is then initialized: -```jsx -nestedContext = AvmContext { - environment: nestedExecutionEnvironment, - machineState: nestedMachineState, - worldState: context.worldState, - worldStateAccessTrace: context.worldStateAccessTrace, - accruedSubstate: { [], ... [], }, // all empty - results: {reverted: false, output: []}, -} -``` ## Gas cost of call instruction From 5496386b31ec5c5c22ef30cbb85f24be7c017ca8 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 22:34:21 +0000 Subject: [PATCH 11/21] cleanup --- yellow-paper/docs/public-vm/context.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/yellow-paper/docs/public-vm/context.mdx b/yellow-paper/docs/public-vm/context.mdx index 44ed4c5ff44..20f68bfdbb7 100644 --- a/yellow-paper/docs/public-vm/context.mdx +++ b/yellow-paper/docs/public-vm/context.mdx @@ -4,7 +4,7 @@ Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). ::: -An **execution context** contains the information and state relevant to a contract call's execution. When a contract call is made, an execution context is initialized before the contract code's execution begins. Context initialization is expanded on in ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls). +An **execution context** contains the information and state relevant to a contract call's execution. When a contract call is made, an execution context is [initialized](#context-initialization) before the contract code's execution begins. #### _AvmContext_ | Field | Type | @@ -18,7 +18,7 @@ An **execution context** contains the information and state relevant to a contra ## Execution Environment -A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it fully specifies the execution environment. This is expanded on in the ["Initial contract calls"](#initial-contract-calls) and ["Nested contract calls"](#nested-contract-calls) sections. +A context's **execution environment** remains constant throughout a contract call's execution. When a contract call initializes its execution context, it [fully specifies the execution environment](#context-initialization). ### _ExecutionEnvironment_ | Field | Type | Description | @@ -48,7 +48,9 @@ When a contract call halts, it sets the context's **contract call results** to c | reverted | `boolean` | | | output | `[field; ]` | | -## Initial contract calls +## Context initialization + +### Initial contract calls An **initial contract call** initializes a new execution context from a public execution request. @@ -115,7 +117,7 @@ INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { } ``` -## Nested contract calls +### Nested contract calls > See the dedicated ("Nested Contract Calls")[./nested-calls] page for a detailed explanation of nested contract calls. From 9fa272584b1809fd1ffda45b68956bbc0704decc Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 23:02:41 +0000 Subject: [PATCH 12/21] comment to move bytecode --- yellow-paper/docs/public-vm/intro.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md index 6cd6a26cbbc..84e4ae68ab4 100644 --- a/yellow-paper/docs/public-vm/intro.md +++ b/yellow-paper/docs/public-vm/intro.md @@ -34,6 +34,8 @@ These sections are meant to provide a high-level definition of the Aztec Virtual ## Public contract bytecode + + A contract's public bytecode is a series of execution instructions for the AVM. Refer to the ["AVM Instruction Set"](./instruction-set) for the details of all supported instructions along with how they modify AVM state. The entirety of a contract's public code is represented as a single block of bytecode with a maximum of `MAX_PUBLIC_INSTRUCTIONS_PER_CONTRACT` ($2^{15} = 32768$) instructions. The mechanism used to distinguish between different "functions" in an AVM bytecode program is left as a higher-level abstraction (_e.g._ similar to Solidity's concept of a function selector). From e8ca28f6e3ef78cb1ab18edd78652c7abf5f4466 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 23:09:13 +0000 Subject: [PATCH 13/21] cleanup --- yellow-paper/docs/public-vm/intro.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md index 84e4ae68ab4..acb7d14f9e5 100644 --- a/yellow-paper/docs/public-vm/intro.md +++ b/yellow-paper/docs/public-vm/intro.md @@ -20,13 +20,14 @@ A **caller** is a contract call's initiator. The caller of an initial contract c These sections are meant to provide a high-level definition of the Aztec Virtual Machine as opposed to a specification of its SNARK implementation. The document therefore mostly omits SNARK or circuit-centric verbiage except when particularly relevant to the high-level architecture. -- [**Public contract bytecode**](#public-contract-bytecode) (aka AVM bytecode) -- [**AVM state**](./state): the state maintained by the AVM -- [**AVM memory model**](./memory-model): the AVM's type-tagged memory model +- [**State**](./state): the state maintained by the AVM +- [**Memory model**](./memory-model): the AVM's type-tagged memory model - [**Execution context**](./context): the AVM's execution context and its initialization for initial contract calls - [**Execution**](#execution): control flow, gas tracking, normal halting, and exceptional halting - [**Nested contract calls**](./nested-calls): the initiation of a contract call from an instruction as well as the processing of nested execution results, gas refunds, and state reverts -- [**AVM Instruction Set**](./instruction-set): the list of all instructions supported by the AVM +- [**Instruction set**](./instruction-set): the list of all instructions supported by the AVM + +> Refer to the ["AVM Bytecode"](./bytecode#avm-bytecode) section of ["Bytecode"](./bytecode) for an explanation of the AVM's bytecode. ## VM Circuit Sections From 08a7db4e9d84bd91be854a4af35d7dbfafb678a7 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 23:14:35 +0000 Subject: [PATCH 14/21] fix links --- yellow-paper/docs/contract-deployment/instances.md | 2 +- yellow-paper/docs/public-vm/instruction-set.mdx | 4 ++-- yellow-paper/docs/public-vm/intro.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/yellow-paper/docs/contract-deployment/instances.md b/yellow-paper/docs/contract-deployment/instances.md index 1f76a5c2827..7c19895dcdc 100644 --- a/yellow-paper/docs/contract-deployment/instances.md +++ b/yellow-paper/docs/contract-deployment/instances.md @@ -145,7 +145,7 @@ Specific to private functions: Specific to public functions: -- The bytecode loaded by the [AVM](../public-vm) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit.md). +- The bytecode loaded by the [AVM](../public-vm/intro) for the contract matches the `bytecode_commitment` in the contract class, verified using the [bytecode validation circuit](../public-vm/bytecode-validation-circuit). - The contract Deployment Nullifier has been emitted, or prove that it hasn't, in which case the transaction is expected to revert. This check is done via a merkle (non-)membership proof of the Deployment Nullifier. Note that a public function should be callable in the same transaction in which its contract Deployment Nullifier was emitted. Note that, since constructors are handled at the application level, the kernel circuit is not required to check the Initialization Nullifier before executing code. diff --git a/yellow-paper/docs/public-vm/instruction-set.mdx b/yellow-paper/docs/public-vm/instruction-set.mdx index 2af70937b98..196c11db8a7 100644 --- a/yellow-paper/docs/public-vm/instruction-set.mdx +++ b/yellow-paper/docs/public-vm/instruction-set.mdx @@ -6,8 +6,8 @@ The following notes are relevant to the table and sections below: - `M[offset]` notation is shorthand for `context.machineState.memory[offset]` - `S[slot]` notation is shorthand for an access to the specified slot in the current contract's public storage (`context.worldState.publicStorage`) after the slot has been siloed by the storage address (`hash(context.environment.storageAddress, slot)`) - Any instruction whose description does not mention a program counter change simply increments it: `context.machineState.pc++` -- All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./avm#gas-limits-and-tracking) -- Any instruction can lead to an exceptional halt as specified in ["Exceptional halting"](./avm#exceptional-halting) +- All instructions update `context.machineState.*GasLeft` as detailed in ["Gas limits and tracking"](./execution#gas-limits-and-tracking) +- Any instruction can lead to an exceptional halt as specified in ["Exceptional halting"](./execution#exceptional-halting) - The term `hash` used in expressions below represents a Poseidon hash operation. - Type structures used in world state tracing operations are defined in ["Type Definitions"](./type-structs) diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md index acb7d14f9e5..d0ec38b9000 100644 --- a/yellow-paper/docs/public-vm/intro.md +++ b/yellow-paper/docs/public-vm/intro.md @@ -27,7 +27,7 @@ These sections are meant to provide a high-level definition of the Aztec Virtual - [**Nested contract calls**](./nested-calls): the initiation of a contract call from an instruction as well as the processing of nested execution results, gas refunds, and state reverts - [**Instruction set**](./instruction-set): the list of all instructions supported by the AVM -> Refer to the ["AVM Bytecode"](./bytecode#avm-bytecode) section of ["Bytecode"](./bytecode) for an explanation of the AVM's bytecode. +> Refer to the ["AVM Bytecode"](../bytecode#avm-bytecode) section of ["Bytecode"](../bytecode) for an explanation of the AVM's bytecode. ## VM Circuit Sections From 2ef6284154a309f71739eedb3056ad3aebe1805b Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Tue, 13 Feb 2024 23:20:09 +0000 Subject: [PATCH 15/21] add delegate call opcode to ts/rs/cpp --- avm-transpiler/src/opcodes.rs | 2 ++ barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp | 1 + barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp | 1 + .../simulator/src/avm/serialization/bytecode_serialization.ts | 1 + .../src/avm/serialization/instruction_serialization.ts | 1 + 5 files changed, 6 insertions(+) diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs index 6948aca6239..6466a240cd5 100644 --- a/avm-transpiler/src/opcodes.rs +++ b/avm-transpiler/src/opcodes.rs @@ -76,6 +76,7 @@ pub enum AvmOpcode { // Control Flow - Contract Calls CALL, STATICCALL, + DELEGATECALL, RETURN, REVERT, @@ -161,6 +162,7 @@ impl AvmOpcode { // Control Flow - Contract Calls AvmOpcode::CALL => "CALL", AvmOpcode::STATICCALL => "STATICCALL", + AvmOpcode::DELEGATECALL => "DELEGATECALL", AvmOpcode::RETURN => "RETURN", AvmOpcode::REVERT => "REVERT", diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp index dbf9a76a741..02a265cc02c 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.cpp @@ -82,6 +82,7 @@ const std::unordered_map Bytecode::OPERANDS_NUM = { //// Control Flow - Contract Calls //{ OpCode::CALL, }, //{ OpCode::STATICCALL, }, + //{ OpCode::DELEGATECALL, }, { OpCode::RETURN, 2 }, // { OpCode::REVERT, }, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp index 634ba203205..e2520ba5448 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp @@ -90,6 +90,7 @@ enum class OpCode : uint8_t { // Control Flow - Contract Calls CALL, STATICCALL, + DELEGATECALL, RETURN, REVERT, diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts index 756d44e906b..b273713182a 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.ts @@ -124,6 +124,7 @@ const INSTRUCTION_SET = () => // Control Flow - Contract Calls [Call.opcode, Call], [StaticCall.opcode, StaticCall], + //[DelegateCall.opcode, DelegateCall], [Return.opcode, Return], [Revert.opcode, Revert], diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts index d4d2a3a32c6..cd8bc9eaeae 100644 --- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts +++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts @@ -61,6 +61,7 @@ export enum Opcode { SENDL2TOL1MSG, CALL, STATICCALL, + DELEGATECALL, RETURN, REVERT, KECCAK, From 46c182d40030f7b8905c35b83dc9ac47db3efd92 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 14 Feb 2024 16:38:28 +0000 Subject: [PATCH 16/21] sidebar cleanup --- yellow-paper/docs/public-vm/circuit-index.md | 3 +++ yellow-paper/docs/public-vm/index.md | 3 +++ yellow-paper/sidebars.js | 17 ++++++++++++----- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 yellow-paper/docs/public-vm/circuit-index.md create mode 100644 yellow-paper/docs/public-vm/index.md diff --git a/yellow-paper/docs/public-vm/circuit-index.md b/yellow-paper/docs/public-vm/circuit-index.md new file mode 100644 index 00000000000..568f41f3723 --- /dev/null +++ b/yellow-paper/docs/public-vm/circuit-index.md @@ -0,0 +1,3 @@ +# AVM Circuit + +The AVM circuit's purpose is to prove execution of a sequence of instructions for a public execution request. Regardless of whether execution succeeds or reverts, the circuit always generates a valid proof of execution. diff --git a/yellow-paper/docs/public-vm/index.md b/yellow-paper/docs/public-vm/index.md new file mode 100644 index 00000000000..98fa1b94eda --- /dev/null +++ b/yellow-paper/docs/public-vm/index.md @@ -0,0 +1,3 @@ +# Aztec (Public) Virtual Machine + +The Aztec Virtual Machine (AVM) executes the public section of a transaction. \ No newline at end of file diff --git a/yellow-paper/sidebars.js b/yellow-paper/sidebars.js index 9340636b477..447505ec054 100644 --- a/yellow-paper/sidebars.js +++ b/yellow-paper/sidebars.js @@ -187,7 +187,7 @@ const sidebars = { { label: "Aztec (Public) VM", type: "category", - link: { type: "doc", id: "public-vm/intro" }, + link: { type: "doc", id: "public-vm/index" }, items: [ "public-vm/intro", "public-vm/state", @@ -196,10 +196,17 @@ const sidebars = { "public-vm/execution", "public-vm/nested-calls", "public-vm/instruction-set", - "public-vm/avm-circuit", - "public-vm/control-flow", - "public-vm/alu", - "public-vm/bytecode-validation-circuit", + { + label: "AVM Circuit", + type: "category", + link: { type: "doc", id: "public-vm/circuit-index" }, + items: [ + "public-vm/avm-circuit", + "public-vm/control-flow", + "public-vm/alu", + "public-vm/bytecode-validation-circuit", + ], + }, "public-vm/type-structs", ], }, From 7f2d2aad11d4a6dd00f0b5ce25e55f7b24f14926 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 14 Feb 2024 16:52:18 +0000 Subject: [PATCH 17/21] pr comments --- yellow-paper/docs/public-vm/context.mdx | 2 +- yellow-paper/docs/public-vm/execution.md | 2 +- yellow-paper/docs/public-vm/gen/_instruction-set.mdx | 12 ++++++------ yellow-paper/docs/public-vm/memory-model.md | 2 +- yellow-paper/docs/public-vm/nested-calls.mdx | 4 ++-- .../src/preprocess/InstructionSet/InstructionSet.js | 12 ++++++------ 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/yellow-paper/docs/public-vm/context.mdx b/yellow-paper/docs/public-vm/context.mdx index 20f68bfdbb7..de1c02a8e6a 100644 --- a/yellow-paper/docs/public-vm/context.mdx +++ b/yellow-paper/docs/public-vm/context.mdx @@ -119,7 +119,7 @@ INITIAL_CONTRACT_CALL_RESULTS = ContractCallResults { ### Nested contract calls -> See the dedicated ("Nested Contract Calls")[./nested-calls] page for a detailed explanation of nested contract calls. +> See the dedicated ["Nested Contract Calls"](./nested-calls) page for a detailed explanation of nested contract calls. import NestedContext from "./_nested-context.md"; diff --git a/yellow-paper/docs/public-vm/execution.md b/yellow-paper/docs/public-vm/execution.md index 1ca63e09961..d09fe43831d 100644 --- a/yellow-paper/docs/public-vm/execution.md +++ b/yellow-paper/docs/public-vm/execution.md @@ -108,7 +108,7 @@ The AVM's exceptional halting conditions area listed below: ``` 1. **Failed memory tag check** - Defined per-instruction in the [Instruction Set](./instruction-set) -1. **Maximum memory index ($2^{32}$) exceeded** +1. **Out of bounds memory access (max memory offset is $2^{32}-1$)** ``` for offset in instr.args.*Offset: assert offset < 2^32 diff --git a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx index 3c941a3cc37..d69bbb6f48b 100644 --- a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx +++ b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx @@ -1290,7 +1290,7 @@ M[dstOffset] = value`} slot: M[slotOffset], exists: exists, // defined above value: value, // defined above - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } )`} @@ -1331,7 +1331,7 @@ context.worldState.publicStorage.set({ callPointer: context.environment.callPointer, slot: M[slotOffset], value: M[srcOffset], - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } )`} @@ -1369,7 +1369,7 @@ M[existsOffset] = exists`} leafIndex: M[leafIndexOffset] leaf: M[leafOffset], exists: exists, // defined above - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } )`} @@ -1438,7 +1438,7 @@ Emit a new note hash to be inserted into the note hash tree TracedNoteHash { callPointer: context.environment.callPointer, value: M[noteHashOffset], // unsiloed note hash - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } )`} @@ -1473,7 +1473,7 @@ M[existsOffset] = exists`} callPointer: context.environment.callPointer, leaf: M[nullifierOffset], exists: exists, // defined above - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } )`} @@ -1505,7 +1505,7 @@ Emit a new nullifier to be inserted into the nullifier tree TracedNullifier { callPointer: context.environment.callPointer, value: M[nullifierOffset], // unsiloed nullifier - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } )`} diff --git a/yellow-paper/docs/public-vm/memory-model.md b/yellow-paper/docs/public-vm/memory-model.md index 444ce2aab82..62c6912a13e 100644 --- a/yellow-paper/docs/public-vm/memory-model.md +++ b/yellow-paper/docs/public-vm/memory-model.md @@ -15,7 +15,7 @@ All data regions are linear blocks of memory where each memory cell stores a fin Main memory stores the internal state of the current program being executed. Can be written to as well as read. -The main memory region stores _type tags_ alongside data values. [Type tags are explained further on in this document](#type tags). +The main memory region stores [_type tags_](#types-and-tagged-memory) alongside data values. #### Calldata diff --git a/yellow-paper/docs/public-vm/nested-calls.mdx b/yellow-paper/docs/public-vm/nested-calls.mdx index 6d84ebfef71..6729fedf550 100644 --- a/yellow-paper/docs/public-vm/nested-calls.mdx +++ b/yellow-paper/docs/public-vm/nested-calls.mdx @@ -19,7 +19,7 @@ context.worldStateAccessTrace.contractCalls.append( callPointer: context.worldStateAccessTrace.contractCalls.length + 1, address: M[addrOffset], storageAddress: M[addrOffset], - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, endLifetime: 0, // The call's end-lifetime will be updated later if it or its caller reverts } ) @@ -78,7 +78,7 @@ context.daGasLeft += nestedContext.machineState.daGasLeft If the call instruction specifies non-zero `retSize`, the caller copies any returned output data to its memory. ```jsx if retSize > 0: - context.memory[retOffset:retOffset+retSize] = nestedContext.results.output + context.machineState.memory[retOffset:retOffset+retSize] = nestedContext.results.output ``` If the nested call succeeded, the caller accepts its world state and accrued substate modifications. diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index 896e248c11f..0c6edd578aa 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -776,7 +776,7 @@ context.worldStateAccessTrace.publicStorageReads.append( slot: M[slotOffset], exists: exists, // defined above value: value, // defined above - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } ) `, @@ -812,7 +812,7 @@ context.worldStateAccessTrace.publicStorageWrites.append( callPointer: context.environment.callPointer, slot: M[slotOffset], value: M[srcOffset], - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } ) `, @@ -847,7 +847,7 @@ context.worldStateAccessTrace.noteHashChecks.append( leafIndex: M[leafIndexOffset] leaf: M[leafOffset], exists: exists, // defined above - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } ) `, @@ -910,7 +910,7 @@ context.worldStateAccessTrace.newNoteHashes.append( TracedNoteHash { callPointer: context.environment.callPointer, value: M[noteHashOffset], // unsiloed note hash - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } ) `, @@ -942,7 +942,7 @@ context.worldStateAccessTrace.nullifierChecks.append( callPointer: context.environment.callPointer, leaf: M[nullifierOffset], exists: exists, // defined above - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } ) `, @@ -971,7 +971,7 @@ context.worldStateAccessTrace.newNullifiers.append( TracedNullifier { callPointer: context.environment.callPointer, value: M[nullifierOffset], // unsiloed nullifier - counter: context.worldStateAccessTrace.accessCounter++, + counter: ++context.worldStateAccessTrace.accessCounter, } ) `, From 1628bb8810590ed98f7602c7ec00da23e09bcabb Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 14 Feb 2024 17:18:37 +0000 Subject: [PATCH 18/21] cleanup --- yellow-paper/docs/public-vm/intro.md | 11 ++++------- yellow-paper/docs/public-vm/state.md | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md index d0ec38b9000..f88e5999ec3 100644 --- a/yellow-paper/docs/public-vm/intro.md +++ b/yellow-paper/docs/public-vm/intro.md @@ -16,9 +16,7 @@ During execution, additional contract calls may be made. While an [**initial con A **caller** is a contract call's initiator. The caller of an initial contract call is an Aztec sequencer. The caller of a nested contract call is the AVM itself executing in the calling context. -## High-level VM Sections - -These sections are meant to provide a high-level definition of the Aztec Virtual Machine as opposed to a specification of its SNARK implementation. The document therefore mostly omits SNARK or circuit-centric verbiage except when particularly relevant to the high-level architecture. +## Outline - [**State**](./state): the state maintained by the AVM - [**Memory model**](./memory-model): the AVM's type-tagged memory model @@ -26,12 +24,11 @@ These sections are meant to provide a high-level definition of the Aztec Virtual - [**Execution**](#execution): control flow, gas tracking, normal halting, and exceptional halting - [**Nested contract calls**](./nested-calls): the initiation of a contract call from an instruction as well as the processing of nested execution results, gas refunds, and state reverts - [**Instruction set**](./instruction-set): the list of all instructions supported by the AVM +- [**AVM Circuit**](./avm-circuit)**: the AVM as a SNARK circuit for proving execution -> Refer to the ["AVM Bytecode"](../bytecode#avm-bytecode) section of ["Bytecode"](../bytecode) for an explanation of the AVM's bytecode. - -## VM Circuit Sections +> The sections prior to the "AVM Circuit" are meant to provide a high-level definition of the Aztec Virtual Machine as opposed to a specification of its SNARK implementation. They therefore mostly omit SNARK or circuit-centric verbiage except when particularly relevant to the high-level architecture. -- **[AVM Circuit](./avm-circuit)** +> Refer to the ["AVM Bytecode"](../bytecode#avm-bytecode) section of ["Bytecode"](../bytecode) for an explanation of the AVM's bytecode. ## Public contract bytecode diff --git a/yellow-paper/docs/public-vm/state.md b/yellow-paper/docs/public-vm/state.md index 436863f12d1..ab9ba694a80 100644 --- a/yellow-paper/docs/public-vm/state.md +++ b/yellow-paper/docs/public-vm/state.md @@ -15,7 +15,7 @@ This section describes the types of state maintained by the AVM. | `daGasLeft` | `field` | Tracks the amount of DA gas remaining at any point during execution. Initialized from contract call arguments. | | `pc` | `field` | Index into the contract's bytecode indicating which instruction to execute. Initialized to 0 during context initialization. | | `internalCallStack` | `Vector` | A stack of program counters pushed to and popped from by `INTERNALCALL` and `INTERNALRETURN` instructions. Initialized as empty during context initialization. | -| `memory` | `[field; 2^32]` | A $2^{32}$ entry memory space accessible by user code (bytecode instructions). All $2^{32}$ entries are assigned default value 0 during context initialization. See ["Memory Model"](./memory-model) for a complete description of AVM memory. | +| `memory` | `[field; 2^32]` | A $2^{32}$ entry memory space accessible by user code (AVM instructions). All $2^{32}$ entries are assigned default value 0 during context initialization. See ["Memory Model"](./memory-model) for a complete description of AVM memory. | ## World State From e3da1fab9d5e2626706e63dbe42918cbe77c4262 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 14 Feb 2024 21:38:05 +0000 Subject: [PATCH 19/21] more clearly define what execution is and separate bytecode from execution context/environment --- yellow-paper/docs/public-vm/avm-circuit.md | 1 - yellow-paper/docs/public-vm/context.mdx | 1 - yellow-paper/docs/public-vm/execution.md | 100 +++++++++++++++---- yellow-paper/docs/public-vm/intro.md | 2 +- yellow-paper/docs/public-vm/nested-calls.mdx | 4 +- yellow-paper/docs/public-vm/state.md | 2 + 6 files changed, 84 insertions(+), 26 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm-circuit.md b/yellow-paper/docs/public-vm/avm-circuit.md index 256ab1b422f..6ab5a2f437d 100644 --- a/yellow-paper/docs/public-vm/avm-circuit.md +++ b/yellow-paper/docs/public-vm/avm-circuit.md @@ -192,7 +192,6 @@ AvmSessionPublicInputs { sessionResults: AvmSessionResults, } ``` -> The `ExecutionEnvironment` structure is defined in [the AVM's execution context](./context). `initialEnvironment` here omits `calldata` and `bytecode`. > The `WorldStateAccessTrace` and `AccruedSubstate` types are defined in ["State"](./state). Their vectors are assigned constant/maximum lengths when used as circuit inputs. diff --git a/yellow-paper/docs/public-vm/context.mdx b/yellow-paper/docs/public-vm/context.mdx index de1c02a8e6a..5b17027b07f 100644 --- a/yellow-paper/docs/public-vm/context.mdx +++ b/yellow-paper/docs/public-vm/context.mdx @@ -85,7 +85,6 @@ INITIAL_EXECUTION_ENVIRONMENT = ExecutionEnvironment { isStaticCall: PublicCallRequest.CallContext.isStaticCall, isDelegateCall: PublicCallRequest.CallContext.isDelegateCall, calldata: PublicCallRequest.args, - bytecode: worldState.contracts[PublicCallRequest.contractAddress], } INITIAL_MACHINE_STATE = MachineState { diff --git a/yellow-paper/docs/public-vm/execution.md b/yellow-paper/docs/public-vm/execution.md index d09fe43831d..b09a3f3eeb4 100644 --- a/yellow-paper/docs/public-vm/execution.md +++ b/yellow-paper/docs/public-vm/execution.md @@ -1,10 +1,66 @@ # Execution, Gas, Halting -Once an execution context has been initialized for a contract call, the [machine state's](./state#machine-state) program counter determines which instruction the AVM executes. For any contract call, the program counter starts at zero, and so instruction execution begins with the very first entry in a contract's bytecode. + +Execution of an AVM program, within a provided [execution context](./context), includes the following steps: + +1. Fetch contract bytecode and decode into a vector of [AVM instructions](./instruction-set) +1. Repeat the next step until a [halt](#halting) is reached +1. Execute the instruction at the index specified by the context's [program counter](#program-counter-and-control-flow) + - Instruction execution will update the program counter + +This routine is represented with the syntax `execute(context)` in ["Nested execution"](./nested-calls#nested-execution) and other sections. + +## Bytecode fetch and decode + +Before execution begins, a contract's bytecode is retrieved. +```jsx +bytecode = context.worldState.contracts[context.environment.address].bytecode +``` + +> As described in ["Contract Deployment"](../contract-deployment), contracts are not stored in a dedicated tree. A [contract class](../contract-deployment/classes) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractClass` structure (which contains the bytecode) and a nullifier representing the class identifier. A [contract instance](../contract-deployment/instances) is [represented](../contract-deployment/classes#registration) as an unencrypted log containing the `ContractInstance` structure and a nullifier representing the contract address. + +> Thus, the syntax used above for bytecode retrieval is shorthand for: +>1. Perform a membership check of the contract instance address nullifier +>1. Retrieve the `ContractInstance` from a database that tracks all such unencrypted logs +> ```jsx +> contractInstance = contractInstances[context.environment.address] +> ``` +>1. Perform a membership check of the contract class identifier nullifier +>1. Retrieve the `ContractClass` and its bytecode from a database that tracks all such unencrypted logs +> ```jsx +> contractClass = contractClasses[contractInstance.contract_class_id] +> bytecode = contractClass.packed_public_bytecode +> ``` + +The bytecode is then decoded into a vector of `instructions`. An instruction is referenced throughout this document according to the following interface: + +| Member | Description | +| --- | --- | +| `opcode` | The 8-bit opcode value that identifies the operation an instruction is meant to perform. | +| `indirect` | Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. | +| `inTag` | The [tag/size](./memory-model.md#tags-and-tagged-memory) to check inputs against and/or tag the destination with. | +| `args` | Named arguments as specified for an instruction in the ["Instruction Set"](./instruction-set). As an example, `instr.args.aOffset` refers to an instructions argument named `aOffset`. | +| `execute` | Apply this instruction's transition function to an execution context (_e.g._ `instr.execute(context)`). | + + +## Instruction execution + +Once bytecode has been fetched and decoded into the `instructions` vector, instruction execution begins. + +The AVM executes the instruction at the index specified by the context's program counter. +```jsx +while (!halted) + instr = instructions[machineState.pc] + instr.execute(context) +``` + +An instruction's execution mutates the context's state as specified in the ["Instruction Set"](./instruction-set). ## Program Counter and Control Flow -The program counter (`machineState.pc`) determines which instruction the AVM executes next (`instr = environment.bytecode[pc]`). Each instruction's execution updates the program counter in some way, which allows the AVM to progress to the next instruction at each step. +A context is initialized with a program counter of zero, and so instruction execution always begins with a contract's the very first instruction. + +The program counter specifies which instruction the AVM will execute next, and each instruction's execution updates the program counter in some way. This allows the AVM to progress to the next instruction at each step. Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./instruction-set#isa-section-jump), [`JUMPI`](./instruction-set#isa-section-jumpi), [`INTERNALCALL`](./instruction-set#isa-section-internalcall)) modify the program counter based on arguments. @@ -42,7 +98,7 @@ machineState.daGasLeft = 0 > Reverting and exceptional halts are covered in more detail in the ["Halting" section](#halting). -## Gas cost notes and examples +### Gas cost notes and examples An instruction's gas cost is meant to reflect the computational cost of generating a proof of its correct execution. For some instructions, this computational cost changes based on inputs. Here are some examples and important notes: - [`JUMP`](./instruction-set/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l1GasCost`, `l2GasCost`, and `daGasCost`. @@ -79,7 +135,7 @@ results.output = machineState.memory[instr.args.retOffset:instr.args.retOffset+i An exceptional halt is not explicitly triggered by an instruction but instead occurs when an exceptional condition is met. -When an exceptional halt occurs, the context is flagged as consuming all of its allocated gas and is marked as `reverted` with no output data, and then execution within the current context ends. +When an exceptional halt occurs, the context is flagged as consuming all of its allocated gas and is marked as `reverted` with _no output data_, and then execution within the current context ends. ``` machineState.l1GasLeft = 0 @@ -99,12 +155,12 @@ The AVM's exceptional halting conditions area listed below: ``` 1. **Invalid instruction encountered** ``` - assert environment.bytecode[machineState.pc].opcode <= MAX_AVM_OPCODE + assert instructions[machineState.pc].opcode <= MAX_AVM_OPCODE ``` -1. **Jump destination past end of bytecode** +1. **Jump destination past end of program** ``` - assert environment.bytecode[machineState.pc].opcode not in {JUMP, JUMPI, INTERNALCALL} - OR instr.args.loc < environment.bytecode.length + assert instructions[machineState.pc].opcode not in {JUMP, JUMPI, INTERNALCALL} + OR instr.args.loc < instructions.length ``` 1. **Failed memory tag check** - Defined per-instruction in the [Instruction Set](./instruction-set) @@ -116,25 +172,25 @@ The AVM's exceptional halting conditions area listed below: 1. **World state modification attempt during a static call** ``` assert !environment.isStaticCall - OR environment.bytecode[machineState.pc].opcode not in WS_AS_MODIFYING_OPS + OR instructions[machineState.pc].opcode not in WS_AS_MODIFYING_OPS ``` > Definition: `WS_AS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state or accrued substate. 1. **Maximum contract call depth (1024) exceeded** ``` assert environment.contractCallDepth <= 1024 - assert environment.bytecode[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} + assert instructions[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} OR environment.contractCallDepth < 1024 ``` 1. **Maximum contract call calls per execution request (1024) exceeded** ``` assert worldStateAccessTrace.contractCalls.length <= 1024 - assert environment.bytecode[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} + assert instructions[machineState.pc].opcode not in {CALL, STATICCALL, DELEGATECALL} OR worldStateAccessTrace.contractCalls.length < 1024 ``` 1. **Maximum internal call depth (1024) exceeded** ``` assert machineState.internalCallStack.length <= 1024 - assert environment.bytecode[machineState.pc].opcode != INTERNALCALL + assert instructions[machineState.pc].opcode != INTERNALCALL OR environment.contractCallDepth < 1024 ``` 1. **Maximum world state accesses (1024-per-category) exceeded** @@ -149,29 +205,29 @@ The AVM's exceptional halting conditions area listed below: AND worldStateAccessTrace.archiveChecks.length <= 1024 // Storage - assert environment.bytecode[machineState.pc].opcode != SLOAD + assert instructions[machineState.pc].opcode != SLOAD OR worldStateAccessTrace.publicStorageReads.length < 1024 - assert environment.bytecode[machineState.pc].opcode != SSTORE + assert instructions[machineState.pc].opcode != SSTORE OR worldStateAccessTrace.publicStorageWrites.length < 1024 // Note hashes - assert environment.bytecode[machineState.pc].opcode != NOTEHASHEXISTS + assert instructions[machineState.pc].opcode != NOTEHASHEXISTS OR noteHashChecks.length < 1024 - assert environment.bytecode[machineState.pc].opcode != EMITNOTEHASH + assert instructions[machineState.pc].opcode != EMITNOTEHASH OR newNoteHashes.length < 1024 // Nullifiers - assert environment.bytecode[machineState.pc].opcode != NULLIFIEREXISTS + assert instructions[machineState.pc].opcode != NULLIFIEREXISTS OR nullifierChecks.length < 1024 - assert environment.bytecode[machineState.pc].opcode != EMITNULLIFIER + assert instructions[machineState.pc].opcode != EMITNULLIFIER OR newNullifiers.length < 1024 // Read L1 to L2 messages - assert environment.bytecode[machineState.pc].opcode != READL1TOL2MSG + assert instructions[machineState.pc].opcode != READL1TOL2MSG OR worldStateAccessTrace.l1ToL2MessagesReads.length < 1024 // Archive tree & Headers - assert environment.bytecode[machineState.pc].opcode != HEADERMEMBER + assert instructions[machineState.pc].opcode != HEADERMEMBER OR archiveChecks.length < 1024 ``` 1. **Maximum accrued substate entries (per-category) exceeded** @@ -180,11 +236,11 @@ The AVM's exceptional halting conditions area listed below: AND accruedSubstate.sentL2ToL1Messages.length <= MAX_SENT_L2_TO_L1_MESSAGES // Unencrypted logs - assert environment.bytecode[machineState.pc].opcode != ULOG + assert instructions[machineState.pc].opcode != ULOG OR unencryptedLogs.length < MAX_UNENCRYPTED_LOGS // Sent L2 to L1 messages - assert environment.bytecode[machineState.pc].opcode != SENDL2TOL1MSG + assert instructions[machineState.pc].opcode != SENDL2TOL1MSG OR sentL2ToL1Messages.length < MAX_SENT_L2_TO_L1_MESSAGES ``` > Note that ideally the AVM should limit the _total_ accrued substate entries per-category instead of the entries per-call. diff --git a/yellow-paper/docs/public-vm/intro.md b/yellow-paper/docs/public-vm/intro.md index f88e5999ec3..cfb8115e1d3 100644 --- a/yellow-paper/docs/public-vm/intro.md +++ b/yellow-paper/docs/public-vm/intro.md @@ -10,7 +10,7 @@ An Aztec transaction may include one or more **public execution requests**. A pu In order to execute public contract bytecode, the AVM requires some context. An [**execution context**](./context) contains all information necessary to initiate AVM execution, including the relevant contract's bytecode and all state maintained by the AVM. A **contract call** initializes an execution context and triggers AVM execution within that context. -Instruction-by-instruction, the AVM [executes](./execution) the bytecode specified in its context. An **instruction** is a bytecode entry that, when executed, modifies the AVM's execution context (in particular its [state](./state)) according to the instruction's definition in the ["AVM Instruction Set"](./instruction-set). Execution within a context ends when the AVM encounters a [**halt**](./execution#halting). +Instruction-by-instruction, the AVM [executes](./execution) the bytecode specified in its context. An **instruction** is a decoded bytecode entry that, when executed, modifies the AVM's execution context (in particular its [state](./state)) according to the instruction's definition in the ["AVM Instruction Set"](./instruction-set). Execution within a context ends when the AVM encounters a [**halt**](./execution#halting). During execution, additional contract calls may be made. While an [**initial contract call**](./context#initial-contract-calls) initializes a new execution context directly from a public execution request, a [**nested contract call**](./nested-calls) occurs _during_ AVM execution and is triggered by a **contract call instruction** ([`CALL`](./instruction-set#isa-section-call), [`STATICCALL`](./instruction-set#isa-section-staticcall), or [`DELEGATECALL`](./instruction-set#isa-section-delegatecall)). It initializes a new execution context (**nested context**) from the current one (**calling context**) and triggers execution within it. When nested call's execution completes, execution proceeds in the calling context. diff --git a/yellow-paper/docs/public-vm/nested-calls.mdx b/yellow-paper/docs/public-vm/nested-calls.mdx index 6729fedf550..f35eb6897c5 100644 --- a/yellow-paper/docs/public-vm/nested-calls.mdx +++ b/yellow-paper/docs/public-vm/nested-calls.mdx @@ -56,11 +56,13 @@ When the nested call halts, it may not have used up its entire gas allocation. A ## Nested execution -Once the execution context is initialized, execution begins in the nested context. Note that this will modify the nested context. +Once the nested call's context is initialized, execution within that context begins. ```jsx execute(nestedContext) ``` +Execution is detailed in ["Execution, Gas, Halting"](./execution). Note that execution mutates the nested context. + ## Updating the calling context after nested call halts The caller checks whether the nested call succeeded, and places the answer in memory. diff --git a/yellow-paper/docs/public-vm/state.md b/yellow-paper/docs/public-vm/state.md index ab9ba694a80..e6b38f427d3 100644 --- a/yellow-paper/docs/public-vm/state.md +++ b/yellow-paper/docs/public-vm/state.md @@ -17,6 +17,8 @@ This section describes the types of state maintained by the AVM. | `internalCallStack` | `Vector` | A stack of program counters pushed to and popped from by `INTERNALCALL` and `INTERNALRETURN` instructions. Initialized as empty during context initialization. | | `memory` | `[field; 2^32]` | A $2^{32}$ entry memory space accessible by user code (AVM instructions). All $2^{32}$ entries are assigned default value 0 during context initialization. See ["Memory Model"](./memory-model) for a complete description of AVM memory. | + + ## World State ### AVM's access to Aztec State From 5b335c99e70aff43da4a8f30114b46ab2a006a5e Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 14 Feb 2024 21:40:45 +0000 Subject: [PATCH 20/21] cleanup --- yellow-paper/docs/public-vm/state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yellow-paper/docs/public-vm/state.md b/yellow-paper/docs/public-vm/state.md index e6b38f427d3..c3dfada2cdd 100644 --- a/yellow-paper/docs/public-vm/state.md +++ b/yellow-paper/docs/public-vm/state.md @@ -57,7 +57,7 @@ The following table defines an AVM context's world state interface: > \* `*CALL` is short for `CALL`/`STATICCALL`/`DELEGATECALL`. -> \* For the purpose of the AVM, the world state's `contracts` entry is readable to retrieve bytecode on context-creation, and it is effectively updated when a new nullifier is created for a contract class identifier or contract address. +> \* For the purpose of the AVM, the world state's `contracts` member is readable for [bytecode fetching](./execution#bytecode-fetch-and-decode), and it is effectively updated when a new contract class or instance is created (along with a nullifier for the contract class identifier or contract address). ### World State Access Trace From ad8f3de71a216e62df5e1c0d69e1acbfff2b0d1d Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Thu, 15 Feb 2024 00:06:42 +0000 Subject: [PATCH 21/21] cleanup nested call instructions --- .../docs/public-vm/_nested-context.md | 22 +++- yellow-paper/docs/public-vm/execution.md | 14 ++- .../docs/public-vm/gen/_instruction-set.mdx | 111 +++++++++++------- .../docs/public-vm/instruction-set.mdx | 2 +- yellow-paper/docs/public-vm/nested-calls.mdx | 54 +++++++-- .../InstructionSet/InstructionSet.js | 51 +++++--- 6 files changed, 172 insertions(+), 82 deletions(-) diff --git a/yellow-paper/docs/public-vm/_nested-context.md b/yellow-paper/docs/public-vm/_nested-context.md index e3b3117fb31..5de8badab2f 100644 --- a/yellow-paper/docs/public-vm/_nested-context.md +++ b/yellow-paper/docs/public-vm/_nested-context.md @@ -1,15 +1,24 @@ -A nested contract call's execution environment and machine state are derived from the caller's context and the call instruction's arguments. +The nested call's execution context is derived from the caller's context and the call instruction's arguments. + +The following shorthand syntax is used to refer to nested context derivation in the ["Instruction Set"](./instruction-set) and other sections: ```jsx -// contract being called into -contract = callingContext.worldState.contracts[M[addrOffset]] +// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } + +isStaticCall = instr.opcode == STATICCALL +isDelegateCall = instr.opcode == DELEGATECALL + +nestedContext = deriveContext(context, instr.args, isStaticCall, isDelegateCall) +``` +Nested context derivation is defined as follows: +```jsx nestedExecutionEnvironment = ExecutionEnvironment { origin: context.origin, sender: isDelegateCall ? context.sender : context.address, address: M[addrOffset], storageAddress: isDelegateCall ? context.storageAddress : M[addrOffset], - portal: contract.portal, + portal: callingContext.worldState.contracts[M[addrOffset]].portal, feePerL1Gas: context.environment.feePerL1Gas, feePerL2Gas: context.environment.feePerL2Gas, feePerDaGas: context.environment.feePerDaGas, @@ -19,7 +28,6 @@ nestedExecutionEnvironment = ExecutionEnvironment { isStaticCall: isStaticCall, isDelegateCall: isDelegateCall, calldata: context.memory[M[argsOffset]:M[argsOffset]+argsSize], - bytecode: contract.bytecode, } nestedMachineState = MachineState { @@ -32,7 +40,7 @@ nestedMachineState = MachineState { } ``` -The nested call's execution context is then initialized. + ```jsx nestedContext = AvmContext { environment: nestedExecutionEnvironment, @@ -43,3 +51,5 @@ nestedContext = AvmContext { results: {reverted: false, output: []}, } ``` + +> `M[offset]` notation is shorthand for `context.machineState.memory[offset]` \ No newline at end of file diff --git a/yellow-paper/docs/public-vm/execution.md b/yellow-paper/docs/public-vm/execution.md index b09a3f3eeb4..7173e12daf0 100644 --- a/yellow-paper/docs/public-vm/execution.md +++ b/yellow-paper/docs/public-vm/execution.md @@ -68,10 +68,18 @@ The `INTERNALCALL` instruction pushes `machineState.pc+1` to `machineState.inter > An instruction will never assign program counter a value from memory (`machineState.memory`). A `JUMP`, `JUMPI`, or `INTERNALCALL` instruction's destination is a constant from the program bytecode. This property allows for easier static program analysis. -## Gas limits and tracking +## Gas checks and tracking > See ["Gas and Fees"](../gas-and-fees) for a deeper dive into Aztec's gas model and for definitions of each type of gas. -Each instruction has an associated `l1GasCost`, `l2GasCost`, and `daGasCost`. Before an instruction is executed, the VM enforces that there is sufficient gas remaining via the following assertions: +Each instruction has an associated `l1GasCost`, `l2GasCost`, and `daGasCost`. The AVM uses these values to enforce that sufficient gas is available before executing an instruction, and to deduct the cost from the context's remaining gas. The process of checking and charging gas is referred to in other sections using the following shorthand: + +```jsx +chargeGas(context, l1GasCost, l2GasCost, daGasCost) +``` + +### Checking gas + +Before an instruction is executed, the VM enforces that there is sufficient gas remaining via the following assertions: ``` assert machineState.l1GasLeft - instr.l1GasCost > 0 assert machineState.l2GasLeft - instr.l2GasCost > 0 @@ -80,6 +88,8 @@ assert machineState.daGasLeft - instr.daGasCost > 0 > Many instructions (like arithmetic operations) have 0 `l1GasCost` and `daGasCost`. Instructions only incur an L1 or DA cost if they modify the [world state](./state#avm-world-state) or [accrued substate](./state#accrued-substate). +### Charging gas + If these assertions pass, the machine state's gas left is decreased prior to the instruction's core execution: ``` diff --git a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx index d69bbb6f48b..ba1c3fe8805 100644 --- a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx +++ b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx @@ -428,33 +428,45 @@ if exists: 0x35 [`CALL`](#isa-section-call) Call into another contract -{`M[successOffset] = call( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize])`} +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false, isDelegateCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`}
0x36 [`STATICCALL`](#isa-section-staticcall) Call into another contract, disallowing World State and Accrued Substate modifications -{`M[successOffset] = staticcall( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize])`} +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=true, isDelegateCall=false) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`}
0x37 [`DELEGATECALL`](#isa-section-delegatecall) Call into another contract, but keep the caller's `sender` and `storageAddress` -{`M[successOffset] = delegatecall( - M[gasOffset], M[gasOffset+1], M[gasOffset+2], - M[addrOffset], - M[argsOffset], M[argsSize], - M[retOffset], M[retSize])`} +{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize } +chargeGas(context, + l1GasCost=M[instr.args.gasOffset], + l2GasCost=M[instr.args.gasOffset+1], + daGasCost=M[instr.args.gasOffset+2]) +traceNestedCall(context, instr.args.addrOffset) +nestedContext = deriveContext(context, instr.args, isStaticCall=false, isDelegateCall=true) +execute(nestedContext) +updateContextAfterNestedCall(context, instr.args, nestedContext)`}