From 49ce589c7802dc5765f2ee6df45faf7ffdb5f27e Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Fri, 15 Dec 2023 19:56:32 +0000 Subject: [PATCH 1/8] pc and control flow in avm yp --- yellow-paper/docs/public-vm/avm.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 406d1369ea76..9bf825fa6040 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -162,3 +162,15 @@ INITIAL_MESSAGE_CALL_RESULTS = MessageCallResults { ``` > Note: unlike memory in the Ethereum Virtual Machine, uninitialized memory in the AVM is not readable! A memory cell must be written (and therefore [type-tagged](./state-model#types-and-tagged-memory)) before it can be read. + +## Execution +With an initialized context (and initial program counter of 0), the AVM can begin execution of a message call starting with the very first instruction in its bytecode. + +### Program Counter and Control Flow +The program counter (machine state's `pc`) determines which instruction to execute (`environment.bytecode[pc]`)Each instruction's state transition function updates the program counter in some way, which allows the VM 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`](./InstructionSet#isa-section-jump), [`JUMPI`](./InstructionSet#isa-section-jumpi), `INTERNALCALL`, `INTERNALRETURN`) modify the program counter based on inputs. + +> Note: program counter will never be assigned to value from memory. Jump destinations can only be constants from the contract bytecode. + +### Gas limits and tracking From 69cd9e9b216eadbe79ce942995dfba554aa425e2 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Fri, 15 Dec 2023 21:12:13 +0000 Subject: [PATCH 2/8] yp avm gas --- yellow-paper/docs/public-vm/avm.md | 36 +++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 9bf825fa6040..6d225dabb2ca 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -164,13 +164,43 @@ INITIAL_MESSAGE_CALL_RESULTS = MessageCallResults { > Note: unlike memory in the Ethereum Virtual Machine, uninitialized memory in the AVM is not readable! A memory cell must be written (and therefore [type-tagged](./state-model#types-and-tagged-memory)) before it can be read. ## Execution -With an initialized context (and initial program counter of 0), the AVM can begin execution of a message call starting with the very first instruction in its bytecode. +With an initialized context (and therefore an initial program counter of 0), the AVM can execute a message call starting with the very first instruction in its bytecode. ### Program Counter and Control Flow -The program counter (machine state's `pc`) determines which instruction to execute (`environment.bytecode[pc]`)Each instruction's state transition function updates the program counter in some way, which allows the VM to progress to the next instruction at each step. +The program counter (machine state's `pc`) determines which instruction to execute (`instr = environment.bytecode[pc]`). Each instruction's state transition function updates the program counter in some way, which allows the VM 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`](./InstructionSet#isa-section-jump), [`JUMPI`](./InstructionSet#isa-section-jumpi), `INTERNALCALL`, `INTERNALRETURN`) modify the program counter based on inputs. -> Note: program counter will never be assigned to value from memory. Jump destinations can only be constants from the contract bytecode. +> Note: program counter will never be assigned to a value from memory. Jump destinations can only be constants from the contract bytecode. ### Gas limits and tracking +Each instruction has an associated `l1GasCost` and `l2GasCost`. 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 +``` + +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 +``` + +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 +``` +> Reverting and exceptional halts will be covered in more detail later. + +### Gas cost notes and examples +A instruction's gas cost is loosely derived from its complexity. Execution complexity of some instructions changes based on inputs. Here are some examples and notes: +- [`JUMP`](./InstructionSet/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l1GasCost` and `l2GasCost`. +- The [`SET`](./InstructionSet/#isa-section-set) instruction operates on a different sized constant (based on its `dst-type`). 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`](./InstructionSet/#isa-section-calldatacopy) argument which copies `copySize` words from `environment.calldata` to memory. +- The [`CALL`](./InstructionSet/#isa-section-call)/`STATICCALL`/`DELEGATECALL` instruction's gas cost is determined by its `l*Gas` arguments, but any gas unused by the triggered message call is refunded after its completion (more on this later). +- An instruction with "offset" arguments (like [`ADD`](./InstructionSet/#isa-section-add) and many others), has increased cost for each offset argument that is flagged as "indirect". + +> Implementation detail: 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. + +> Implementation detail: an instruction's gas cost takes into account the costs of associated downstream computations. So, 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). An instruction that triggers a nested message call (`CALL`/`STATICCALL`/`DELEGATECALL`) incurs a cost accounting for the nested call's execution and an added execution of the public kernel circuit. \ No newline at end of file From 6f5b856e44d04a693aabf6a3768947a3c6127f2e Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Sat, 16 Dec 2023 10:44:17 -0500 Subject: [PATCH 3/8] avm yp halting --- yellow-paper/docs/public-vm/avm.md | 34 +++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 6d225dabb2ca..f111ce3475b9 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -203,4 +203,36 @@ A instruction's gas cost is loosely derived from its complexity. Execution compl > Implementation detail: 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. -> Implementation detail: an instruction's gas cost takes into account the costs of associated downstream computations. So, 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). An instruction that triggers a nested message call (`CALL`/`STATICCALL`/`DELEGATECALL`) incurs a cost accounting for the nested call's execution and an added execution of the public kernel circuit. \ No newline at end of file +> Implementation detail: an instruction's gas cost takes into account the costs of associated downstream computations. So, 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). An instruction that triggers a nested message call (`CALL`/`STATICCALL`/`DELEGATECALL`) incurs a cost accounting for the nested call's execution and an added execution of the public kernel circuit. + +## Halting +A message call's execution can end with a **normal halt** or **exceptional halt**. A normal halt occurs when a [`RETURN`](./InstructionSet/#isa-section-return) or [`REVERT`](./InstructionSet/#isa-section-revert) instruction is encountered. An exceptional halt is not explicitly triggered by an instruction but instead occurs when one of the following conditions is met: +1. **Insufficient gas** + ``` + assert machineState.l1GasLeft - instr.l1GasCost > 0 + assert machineState.l2GasLeft - instr.l2GasCost > 0 + ``` +1. **Invalid instruction encountered** + ``` + assert environment.bytecode[machineState.pc].opcode <= MAX_AVM_OPCODE + ``` +1. **Failed memory tag check** + - Defined per-instruction in the [Instruction Set](./InstructionSet/#isa-section-call) +1. **Jump destination past end of bytecode** + ``` + assert machineState.pc >= environment.bytecode.length + ``` +1. **World state modification attempt during a static call** + ``` + assert !environment.isStaticCall + or environment.bytecode[machineState.pc].opcode not in WS_MODIFYING_OPS + ``` + > Definition: `WS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state. + +When an exceptional halt occurs, the context is flagged as consuming all off its allocated gas and marked as `reverted` with no output data: +``` +machineState.l1GasLeft = 0 +machineState.l2GasLeft = 0 +results.reverted = true +// results.output remains undefined +``` \ No newline at end of file From e6536c3e5925f90d46d5cdcb062e6fa165b086eb Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Sat, 16 Dec 2023 10:56:37 -0500 Subject: [PATCH 4/8] avm yp bolded keywords, link fix --- yellow-paper/docs/public-vm/avm.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index f111ce3475b9..7a03df30cf66 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -12,13 +12,13 @@ Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](ht ::: ## Introduction -An Aztec transaction may include one or more public execution requests. A public execution request represents an initial "message call" to a contract, providing input data and triggering the execution of that contract's public code in the Aztec Virtual Machine. Given a message call to a contract, the AVM executes the corresponding code one instruction at a time, treating each instruction as a transition function on its state. +An Aztec transaction may include one or more **public execution requests**. A public execution request represents an initial **message call** to a contract, providing input data and triggering the execution of that contract's public code in the Aztec Virtual Machine. Given a message call to a contract, the AVM executes the corresponding code one instruction at a time, treating each instruction as a transition function on its state. > Public execution requests may originate as [`enqueuedPublicFunctionCalls`](../calls/enqueued-calls.md) triggered during the transaction's private execution. This document contains the following sections: - **Public contract bytecode** (aka AVM bytecode) -- **Execution Context**, outlining the AVM's environment and state +- **Execution context**, outlining the AVM's environment and state - **Execution**, outlining control flow, gas tracking, halting, and reverting - **Nested calls**, outlining the initiation of message calls, processing of sub-context results, gas refunds, and world state reverts @@ -38,7 +38,7 @@ A contract's public bytecode is a series of execution instructions for the AVM. 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 necessary to initiate AVM execution along with the state maintained by the AVM throughout execution: +An **execution context** includes the information necessary to initiate AVM execution along with the state maintained by the AVM throughout execution: ``` AVMContext { environment: ExecutionEnvironment, @@ -49,11 +49,11 @@ AVMContext { } ``` -The first two entries, "Execution Environment" and "Machine State", share the same lifecycle. They contain information pertaining to a single message call and are initialized prior to the start of a call's execution. +The first two entries, **execution environment** and **machine state**, share the same lifecycle. They contain information pertaining to a single message call and are initialized prior to the start of a call's execution. > When a nested message call is made, a new environment and machine state are initialized by the caller. In other words, a nested message call has its own environment and machine state which are _partially_ derived from the caller's context. -The "Execution Environment" is fully specified by a message call's execution agent and remains constant throughout a call's execution. +The **execution environment** is fully specified by a message call's execution agent and remains constant throughout a call's execution. ``` ExecutionEnvironment { address, @@ -73,7 +73,7 @@ ExecutionEnvironment { } ``` -"Machine State" is partially specified by the execution agent, and otherwise begins as empty or uninitialized for each message call. This state is transformed on an instruction-per-instruction basis. +**Machine state** is partially specified by the execution agent, and otherwise begins as empty or uninitialized for each message call. This state is transformed on an instruction-per-instruction basis. ``` MachineState { l1GasLeft, @@ -83,7 +83,7 @@ MachineState { } ``` -"World State" contains persistable VM state. If a message call succeeds, its world state updates are applied to the calling context (whether that be a parent call's context or the transaction context). If a message call fails, its world state updates are rejected by its caller. When a _transaction_ succeeds, its world state updates persist into future transactions. +**World state** contains persistable VM state. If a message call succeeds, its world state updates are applied to the calling context (whether that be a parent call's context or the transaction context). If a message call fails, its world state updates are rejected by its caller. When a _transaction_ succeeds, its world state updates persist into future transactions. ``` WorldState { publicStorage: (address, slot) => value, // read/write @@ -98,7 +98,7 @@ WorldState { > Note: each member of the world state is implemented as an independent merkle tree with different properties. -The "Accrued Substate", as coined in the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper), contains information that is accrued throughout transaction execution to be "acted upon immediately following the transaction." These are append-only arrays containing state that is not relevant to other calls or transactions. Similar to world state, if a message call succeeds, its substate is appended to its calling context, but if it fails its substate is dropped by its caller. +The **accrued substate**, as coined in the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper), contains information that is accrued throughout transaction execution to be "acted upon immediately following the transaction." These are append-only arrays containing state that is not relevant to other calls or transactions. Similar to world state, if a message call succeeds, its substate is appended to its calling context, but if it fails its substate is dropped by its caller. ``` AccruedSubstate { logs: [], // append-only @@ -106,7 +106,7 @@ AccruedSubstate { } ``` -Finally, when a message call halts, it sets the context's "Message Call Results" to communicate results to the caller. +Finally, when a message call halts, it sets the context's **message call results** to communicate results to the caller. ``` MessageCallResults { reverted: boolean, @@ -217,7 +217,7 @@ A message call's execution can end with a **normal halt** or **exceptional halt* assert environment.bytecode[machineState.pc].opcode <= MAX_AVM_OPCODE ``` 1. **Failed memory tag check** - - Defined per-instruction in the [Instruction Set](./InstructionSet/#isa-section-call) + - Defined per-instruction in the [Instruction Set](./InstructionSet) 1. **Jump destination past end of bytecode** ``` assert machineState.pc >= environment.bytecode.length From 1cfa3705535ac31693e99064f5cf0f032c0e1bb4 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Sat, 16 Dec 2023 16:04:37 -0500 Subject: [PATCH 5/8] avm yp finish halting section --- yellow-paper/docs/public-vm/avm.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 7a03df30cf66..275058c16fe4 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -206,7 +206,20 @@ A instruction's gas cost is loosely derived from its complexity. Execution compl > Implementation detail: an instruction's gas cost takes into account the costs of associated downstream computations. So, 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). An instruction that triggers a nested message call (`CALL`/`STATICCALL`/`DELEGATECALL`) incurs a cost accounting for the nested call's execution and an added execution of the public kernel circuit. ## Halting -A message call's execution can end with a **normal halt** or **exceptional halt**. A normal halt occurs when a [`RETURN`](./InstructionSet/#isa-section-return) or [`REVERT`](./InstructionSet/#isa-section-revert) instruction is encountered. An exceptional halt is not explicitly triggered by an instruction but instead occurs when one of the following conditions is met: +A message call'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`](./InstructionSet/#isa-section-return) or [`REVERT`](./InstructionSet/#isa-section-revert)). Such instructions consume gas normally and optionally initialize some output data before finally halting execution within the current context. +``` +machineState.l1GasLeft -= instr.l1GasCost +machineState.l2GasLeft -= instr.l2GasCost +// results.reverted remains false +results.output = machineState.memory[instr.retOffset:instr.retOffset+instr.retSize] +``` +> Definitions: `retOffset` and `retSize` here are arguments to the [`RETURN`](./InstructionSet/#isa-section-return) and [`REVERT`](./InstructionSet/#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. + +### Exceptional halting +An exceptional halt is not explicitly triggered by an instruction but instead occurs when one of the following halting conditions is met: 1. **Insufficient gas** ``` assert machineState.l1GasLeft - instr.l1GasCost > 0 @@ -229,10 +242,10 @@ A message call's execution can end with a **normal halt** or **exceptional halt* ``` > Definition: `WS_MODIFYING_OPS` represents the list of all opcodes corresponding to instructions that modify world state. -When an exceptional halt occurs, the context is flagged as consuming all off its allocated gas and marked as `reverted` with no output data: +When an exceptional halt occurs, the context is flagged as consuming all off its allocated gas and marked as `reverted` with no output data, and then execution within the current context ends. ``` machineState.l1GasLeft = 0 machineState.l2GasLeft = 0 results.reverted = true // results.output remains undefined -``` \ No newline at end of file +``` From a0b4a6f17635c75bb608a8b0d4c6fcad99bba7a1 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Sun, 17 Dec 2023 21:56:29 -0500 Subject: [PATCH 6/8] nested context initialization --- yellow-paper/docs/public-vm/avm.md | 67 +++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 275058c16fe4..97ef05a6d579 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -86,11 +86,11 @@ MachineState { **World state** contains persistable VM state. If a message call succeeds, its world state updates are applied to the calling context (whether that be a parent call's context or the transaction context). If a message call fails, its world state updates are rejected by its caller. When a _transaction_ succeeds, its world state updates persist into future transactions. ``` WorldState { - publicStorage: (address, slot) => value, // read/write - noteHashes: (address, index) => noteHash, // read & append only - nullifiers: (address, index) => nullifier, // read & append only - l1l2messageHashes: (address, key) => messageHash, // read only - contracts: (address) => bytecode, // read only + publicStorage: (address, slot) => value, // read/write + noteHashes: (address, index) => noteHash, // read & append only + nullifiers: (address, index) => nullifier, // read & append only + l1l2messageHashes: (address, key) => messageHash, // read only + contracts: (address) => {bytecode, portalAddress}, // read only } ``` @@ -214,7 +214,7 @@ A normal halt occurs when the VM encounters an explicit halting instruction ([`R machineState.l1GasLeft -= instr.l1GasCost machineState.l2GasLeft -= instr.l2GasCost // results.reverted remains false -results.output = machineState.memory[instr.retOffset:instr.retOffset+instr.retSize] +results.output = machineState.memory[instr.args.retOffset:instr.args.retOffset+instr.args.retSize] ``` > Definitions: `retOffset` and `retSize` here are arguments to the [`RETURN`](./InstructionSet/#isa-section-return) and [`REVERT`](./InstructionSet/#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. @@ -249,3 +249,58 @@ machineState.l2GasLeft = 0 results.reverted = true // results.output remains undefined ``` + +## Nested calls +During a message call's execution, an instruction may be encountered that triggers another message call. Such instructions include [`CALL`](./InstructionSet/#isa-section-call), [`STATICCALL`](./InstructionSet/#isa-section-staticcall), and `DELEGATECALL`. This may be referred to as a **nested call** as it is a message call initiated from within another. + + +### Initializing context for nested call +Initiation of a nested call requires the creation of a new context (or **sub-context**). +``` +subContext = AVMContext { + environment: nestedExecutionEnvironment, // defined below + machineState: nestedMachineState, // defined below + worldState: callingContext.worldState, + accruedSubstate: empty, + results: INITIAL_MESSAGE_CALL_RESULTS, +} +``` +While some context members like initialized as empty (as they are for an initial message call), other entries are derived from the calling context or from the message call instruction's arguments (`instr.args`). + +The world state is forwarded as-is to the sub-context. Any updates made to the world state before this message call instruction was encountered are carried forward into the sub-context. + +The environment and machine state for the new sub-context are initialized as shown below. Here, the `callingContext` refers to the context in which the nested message call instruction was encountered. +``` +// some assignments reused below +isStaticCall = instr.opcode == STATICCALL_OP +isDelegateCall = instr.opcode == DELEGATECALL_OP +contract = callingContext.worldState.contracts[instr.args.addr] + +nestedExecutionEnvironment = ExecutionEnvironment { + address: instr.args.addr, + storageAddress: isDelegateCall ? callingContext.environment.storageAddress : instr.args.addr, + origin: callingContext.origin, + l1GasPrice: callingContext.l1GasPrice, + l2GasPrice: callingContext.l2GasPrice, + calldata: instr.args.calldata, + sender: callingContext.address, + portal: contract.portal, + bytecode: contract.bytecode, + blockHeader: callingContext.blockHeader, + globalVariables: callingContext.globalVariables, + messageCallDepth: callingContext.messageCallDepth + 1, + isStaticCall: isStaticCall, + isDelegateCall: isDelegateCall, +} + +nestedMachineState = MachineState { + l1GasLeft: callingContext.machineState.memory[instr.args.gasOffset], + l2GasLeft: callingContext.machineState.memory[instr.args.gasOffset+1], + pc: 0, + memory: uninitialized, +} +``` +> Note: recall that `INITIAL_MESSAGE_CALL_RESULTS` is the same initial value used during context initialization for a public execution request's initial message call. +> `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 \ No newline at end of file From fea657346d4e38cf240681137fb937432b2d54e8 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 18 Dec 2023 17:41:32 -0500 Subject: [PATCH 7/8] updates after pr feedback --- yellow-paper/docs/public-vm/avm.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index 97ef05a6d579..aa451a360572 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -17,10 +17,10 @@ An Aztec transaction may include one or more **public execution requests**. A pu > Public execution requests may originate as [`enqueuedPublicFunctionCalls`](../calls/enqueued-calls.md) triggered during the transaction's private execution. This document contains the following sections: -- **Public contract bytecode** (aka AVM bytecode) -- **Execution context**, outlining the AVM's environment and state -- **Execution**, outlining control flow, gas tracking, halting, and reverting -- **Nested calls**, outlining the initiation of message calls, processing of sub-context results, gas refunds, and world state reverts +- [**Public contract bytecode**](#public-contract-bytecode) (aka AVM bytecode) +- [**Execution context**](#execution-context), outlining the AVM's environment and state +- [**Execution**](#execution), outlining control flow, gas tracking, halting, and reverting +- [**Nested calls**](#nested-calls), outlining the initiation of message calls, processing of sub-context results, gas refunds, and world state reverts The **["AVM Instruction Set"](./InstructionSet)** document supplements this one with the list of all supported instructions and their associated state transition functions. @@ -33,6 +33,8 @@ A contract's public bytecode is a series of execution instructions for the AVM. > Note: See the [Bytecode Validation Circuit](./bytecode-validation-circuit.md) to see how a contract's bytecode can be validated and committed to. +See our ["Bytecode"](/bytecode) section for more information. + ## Execution Context :::note REMINDER Many terms and definitions here are borrowed from the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). @@ -115,8 +117,7 @@ MessageCallResults { ``` ### Context initialization for initial call -This section outlines AVM context initialization specifically for a **public execution request's initial message call** (_i.e._ not a nested message call). Context initialization for nested message calls will be explained in a later section. - +This section outlines AVM context initialization specifically for a **public execution request's initial message call** (_i.e._ not a nested message call). Context initialization for nested message calls will be explained [in a later section](#context-initialization-for-a-nested-call). When AVM execution is initiated for a public execution request, the AVM context is initialized as follows: ``` context = AVMContext { @@ -171,14 +172,13 @@ The program counter (machine state's `pc`) determines which instruction to execu Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./InstructionSet#isa-section-jump), [`JUMPI`](./InstructionSet#isa-section-jumpi), `INTERNALCALL`, `INTERNALRETURN`) modify the program counter based on inputs. -> Note: program counter will never be assigned to a value from memory. Jump destinations can only be constants from the contract bytecode. - ### Gas limits and tracking Each instruction has an associated `l1GasCost` and `l2GasCost`. 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 ``` +> Note: many instructions (like arithmetic operations) have 0 `l1GasCost`. Instructions only incur an L1 cost if they modify world state or accrued substate. If these assertions pass, the machine state's gas left is decreased prior to the instruction's core execution: ``` @@ -191,14 +191,14 @@ If either of these assertions _fail_ for an instruction, this triggers an except machineState.l1GasLeft = 0 machineState.l2GasLeft = 0 ``` -> Reverting and exceptional halts will be covered in more detail later. +> Reverting and exceptional halts will be covered in more detail [in a later section](#halting). ### Gas cost notes and examples -A instruction's gas cost is loosely derived from its complexity. Execution complexity of some instructions changes based on inputs. Here are some examples and notes: +A instruction's gas cost is loosely derived from its complexity. Execution complexity of some instructions changes based on inputs. Here are some examples and important notes: - [`JUMP`](./InstructionSet/#isa-section-jump) is an example of an instruction with constant gas cost. Regardless of its inputs, the instruction always incurs the same `l1GasCost` and `l2GasCost`. - The [`SET`](./InstructionSet/#isa-section-set) instruction operates on a different sized constant (based on its `dst-type`). 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`](./InstructionSet/#isa-section-calldatacopy) argument which copies `copySize` words from `environment.calldata` to memory. -- The [`CALL`](./InstructionSet/#isa-section-call)/`STATICCALL`/`DELEGATECALL` instruction's gas cost is determined by its `l*Gas` arguments, but any gas unused by the triggered message call is refunded after its completion (more on this later). +- The [`CALL`](./InstructionSet/#isa-section-call)/[`STATICCALL`](./InstructionSet/#isa-section-call)/`DELEGATECALL` instruction's gas cost is determined by its `l*Gas` arguments, but any gas unused by the triggered message call is refunded after its completion (more on this later). - An instruction with "offset" arguments (like [`ADD`](./InstructionSet/#isa-section-add) and many others), has increased cost for each offset argument that is flagged as "indirect". > Implementation detail: 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. @@ -251,10 +251,10 @@ results.reverted = true ``` ## Nested calls -During a message call's execution, an instruction may be encountered that triggers another message call. Such instructions include [`CALL`](./InstructionSet/#isa-section-call), [`STATICCALL`](./InstructionSet/#isa-section-staticcall), and `DELEGATECALL`. This may be referred to as a **nested call** as it is a message call initiated from within another. +During a message call's execution, an instruction may be encountered that triggers another message call. A message call triggered in this way may be referred to as a **nested call**. The purpose of the [`CALL`](./InstructionSet/#isa-section-call), [`STATICCALL`](./InstructionSet/#isa-section-staticcall), and `DELEGATECALL` instructions is to initiate nested calls. -### Initializing context for nested call +### Context initialization for a nested call Initiation of a nested call requires the creation of a new context (or **sub-context**). ``` subContext = AVMContext { @@ -265,7 +265,7 @@ subContext = AVMContext { results: INITIAL_MESSAGE_CALL_RESULTS, } ``` -While some context members like initialized as empty (as they are for an initial message call), other entries are derived from the calling context or from the message call instruction's arguments (`instr.args`). +While some context members are initialized as empty (as they are for an initial message call), other entries are derived from the calling context or from the message call instruction's arguments (`instr.args`). The world state is forwarded as-is to the sub-context. Any updates made to the world state before this message call instruction was encountered are carried forward into the sub-context. @@ -300,7 +300,7 @@ nestedMachineState = MachineState { memory: uninitialized, } ``` -> Note: recall that `INITIAL_MESSAGE_CALL_RESULTS` is the same initial value used during context initialization for a public execution request's initial message call. +> Note: recall that `INITIAL_MESSAGE_CALL_RESULTS` is the same initial value used during [context initialization for a public execution request's initial message call](#context-initialization-for-initial-call). > `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 \ No newline at end of file From 873c3aecb072782a5817a38d9211b5242f1d56de Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Mon, 18 Dec 2023 18:02:33 -0500 Subject: [PATCH 8/8] fixes from PR --- yellow-paper/docs/public-vm/avm.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/yellow-paper/docs/public-vm/avm.md b/yellow-paper/docs/public-vm/avm.md index aa451a360572..0ac65539a7a9 100644 --- a/yellow-paper/docs/public-vm/avm.md +++ b/yellow-paper/docs/public-vm/avm.md @@ -22,7 +22,9 @@ This document contains the following sections: - [**Execution**](#execution), outlining control flow, gas tracking, halting, and reverting - [**Nested calls**](#nested-calls), outlining the initiation of message calls, processing of sub-context results, gas refunds, and world state reverts -The **["AVM Instruction Set"](./InstructionSet)** document supplements this one with the list of all supported instructions and their associated state transition functions. +Refer to the **["AVM Instruction Set"](./InstructionSet)** for the list of all supported instructions and their associated state transition functions. + +For details on the AVM's "tagged" memory model, refer to the **["AVM Memory Model"](./state-model.md)**. > Note: The Aztec Virtual Machine, while designed with a SNARK implementation in mind, is not strictly tied to any particular implementation and therefore is defined without SNARK or circuit-centric verbiage. That being said, considerations for a SNARK implementation are raised or linked when particularly relevant or helpful. @@ -33,7 +35,7 @@ A contract's public bytecode is a series of execution instructions for the AVM. > Note: See the [Bytecode Validation Circuit](./bytecode-validation-circuit.md) to see how a contract's bytecode can be validated and committed to. -See our ["Bytecode"](/bytecode) section for more information. +Refer to ["Bytecode"](/docs/bytecode) for more information. ## Execution Context :::note REMINDER @@ -172,6 +174,8 @@ The program counter (machine state's `pc`) determines which instruction to execu Most instructions simply increment the program counter by 1. This allows VM execution to flow naturally from instruction to instruction. Some instructions ([`JUMP`](./InstructionSet#isa-section-jump), [`JUMPI`](./InstructionSet#isa-section-jumpi), `INTERNALCALL`, `INTERNALRETURN`) modify the program counter based on inputs. +`JUMP`, `JUMPI`, and `INTERNALCALL` assign a new value to program counter from a constant present in the bytecode. These instructions never assign a value from memory to program counter. Before jumping, the `INTERNALCALL` instruction pushes the current program counter to an internal call-stack that is maintained in a reserved region of memory. `INTERNALRETURN` pops a destination from that internal call-stack and jumps there. Thus, jump destinations, can be either constants from the contract bytecode, or destinations popped from the internal call-stack. + ### Gas limits and tracking Each instruction has an associated `l1GasCost` and `l2GasCost`. Before an instruction is executed, the VM enforces that there is sufficient gas remaining via the following assertions: ```