diff --git a/barretenberg/cpp/pil/vm2/execution.pil b/barretenberg/cpp/pil/vm2/execution.pil index 4a12e9719e6a..da0e94fa83fb 100644 --- a/barretenberg/cpp/pil/vm2/execution.pil +++ b/barretenberg/cpp/pil/vm2/execution.pil @@ -537,7 +537,7 @@ sel_gas_sstore * (written_slots_tree_siloing_separator - constants.DOM_SEP__PUBL #[CHECK_WRITTEN_STORAGE_SLOT] sel_gas_sstore { dynamic_da_gas_factor, - register[1], // value + register[1], // slot prev_written_public_data_slots_tree_root, written_slots_tree_height, sel_gas_sstore, // sel_silo = 1 diff --git a/barretenberg/cpp/pil/vm2/opcodes/emit_notehash.pil b/barretenberg/cpp/pil/vm2/opcodes/emit_notehash.pil index ba5b9374a7eb..61c3006979bd 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/emit_notehash.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/emit_notehash.pil @@ -1,8 +1,42 @@ include "../constants_gen.pil"; -include "../range_check.pil"; include "../trees/note_hash_tree_check.pil"; -namespace execution; // this is a virtual gadget that shares rows with the execution trace +/** + * This virtual gadget implements the EmitNoteHash opcode, which writes a new note hash + * to the note hash tree. The note hash is siloed and made unique by the note_hash_tree_check + * gadget before being inserted as a leaf. It also increments the note hash tree size + * and the number of note hashes emitted. + * + * The opcode is gated by the `sel_execute_emit_notehash` selector, which is set to 1 if the + * EmitNoteHash opcode has reached opcode dispatch (there are no earlier errors). + * + * This opcode also uses: + * - register[0]: the note hash input register (FF) (from execution.pil) + * - is_static: whether the current context is static (from context.pil) + * - contract_address: address of the current contract (from context.pil) + * - prev_note_hash_tree_root: root before the write (from context.pil) + * - prev_note_hash_tree_size: leaf index for the new note hash (from context.pil) + * - prev_num_note_hashes_emitted: count before this emission (from context.pil) + * - note_hash_tree_root: root after the write (from context.pil) + * - note_hash_tree_size: tree size after the write (from context.pil) + * - num_note_hashes_emitted: count after this emission (from context.pil) + * - sel_opcode_error: whether an opcode error was raised (from execution.pil) + * - discard: whether the transaction will be discarded (reverted) (from discard.pil) + * + * Interactions: + * - The gadget performs a lookup into the note_hash_tree_check gadget to write the note hash. + * - The lookup passes sel_silo=1 and sel_unique=1 so the tree check gadget silos the + * note hash with the contract address and makes it unique with the note hash index. + * - This lookup is only performed if no error occurs (sel_write_note_hash = 1). + * + * Errors: + * - If the max note hash writes limit has been reached, the operation raises an opcode error. + * - If the current context is static, the operation raises an opcode error. + * + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_emit_notehash). + */ +namespace execution; #[skippable_if] sel_execute_emit_notehash = 0; // from execution.pil. @@ -15,7 +49,7 @@ namespace execution; // this is a virtual gadget that shares rows with the execu pol commit sel_reached_max_note_hashes; // @boolean sel_reached_max_note_hashes * (1 - sel_reached_max_note_hashes) = 0; - pol commit remaining_note_hashes_inv; + pol commit remaining_note_hashes_inv; // @zero-check // We reached the max note hashes if REMAINING_NOTE_HASH_WRITES is 0 #[MAX_NOTE_HASHES_REACHED] sel_execute_emit_notehash * (REMAINING_NOTE_HASH_WRITES * (sel_reached_max_note_hashes * (1 - remaining_note_hashes_inv) + remaining_note_hashes_inv) - 1 + sel_reached_max_note_hashes) = 0; diff --git a/barretenberg/cpp/pil/vm2/opcodes/emit_nullifier.pil b/barretenberg/cpp/pil/vm2/opcodes/emit_nullifier.pil index 70f44afb1a9d..9cc732e3db34 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/emit_nullifier.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/emit_nullifier.pil @@ -1,28 +1,43 @@ include "../constants_gen.pil"; /** - * This virtual gadget implements the EmitNullifier opcode, which attempts to write a new nullifier - * to the nullifier tree for the current contract address. It also increments the nullifier tree size + * This virtual gadget implements the EmitNullifier opcode, which writes a new nullifier + * to the nullifier tree. The nullifier is siloed with the contract address by the + * indexed_tree_check gadget before being inserted. It also increments the nullifier tree size * and the number of nullifiers emitted. * * The opcode is gated by the `sel_execute_emit_nullifier` selector, which is set to 1 if the - * EmitNullifier opcode has reached dispatch (there are no earlier errors). + * EmitNullifier opcode has reached opcode dispatch (there are no earlier errors). * - * This opcode uses: - * - register[0] as the nullifier input register (FF) + * This opcode also uses: + * - register[0]: the nullifier input register (FF) (from execution.pil) + * - is_static: whether the current context is static (from context.pil) + * - contract_address: address of the current contract (from context.pil) + * - prev_nullifier_tree_root: root before the write (from context.pil) + * - prev_nullifier_tree_size: leaf index for the new nullifier (from context.pil) + * - prev_num_nullifiers_emitted: count before this emission (from context.pil) + * - nullifier_tree_root: root after the write (from context.pil) + * - nullifier_tree_size: tree size after the write (from context.pil) + * - num_nullifiers_emitted: count after this emission (from context.pil) + * - nullifier_tree_height: height of the nullifier tree (from execution.pil) + * - sel_opcode_error: whether an opcode error was raised (from execution.pil) + * - discard: whether the transaction will be discarded (reverted) (from discard.pil) * * Interactions: - * - The gadget performs a lookup into the indexed_tree_check gadget to attempt to write the nullifier. - * - This lookup is only performed if no limit error occurs. + * - The gadget performs a lookup into the indexed_tree_check gadget to write the nullifier. + * - The lookup passes sel_silo=1 so the tree check gadget silos the nullifier + * with the contract address. + * - This lookup is only performed if no error occurs (sel_write_nullifier = 1). * * Errors: * - If the max nullifier writes limit has been reached, the operation raises an opcode error. + * - If the current context is static, the operation raises an opcode error. * - If the nullifier already exists (collision), the operation raises an opcode error. * * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), - * all selectors should be 0 and intermediate values should be 0. + * all columns in this trace should be 0 (gated by sel_execute_emit_nullifier). */ -namespace execution; // this is a virtual gadget that shares rows with the execution trace +namespace execution; // No relations will be checked if this identity is satisfied. #[skippable_if] @@ -36,7 +51,7 @@ namespace execution; // this is a virtual gadget that shares rows with the execu pol commit sel_reached_max_nullifiers; // @boolean sel_reached_max_nullifiers * (1 - sel_reached_max_nullifiers) = 0; - pol commit remaining_nullifiers_inv; + pol commit remaining_nullifiers_inv; // @zero-check // Limit error if REMAINING_NULLIFIER_WRITES is 0 #[MAX_NULLIFIER_WRITES_REACHED] sel_execute_emit_nullifier * (REMAINING_NULLIFIER_WRITES * (sel_reached_max_nullifiers * (1 - remaining_nullifiers_inv) + remaining_nullifiers_inv) - 1 + sel_reached_max_nullifiers) = 0; diff --git a/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil b/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil index 316d8b82b490..449e4b79e5ac 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/get_env_var.pil @@ -1,70 +1,69 @@ include "../public_inputs.pil"; +include "../precomputed.pil"; /** - * This virtual gadget is used to retrieve environment variables from the current execution/context/gas row - * or from public inputs. + * This virtual gadget implements the GetEnvVar opcode, which retrieves an environment + * variable and writes it to the output register. The value is sourced either from the + * current execution row (context/gas columns) or from public inputs, depending on + * the requested variable. * - * The opcode is gated by the `sel_should_get_env_var` selector, which is set to 1 if the GetEnvVar opcode - * has reached dispatch (there are no earlier errors). + * The opcode is gated by the `sel_execute_get_env_var` selector, which is set to 1 if the + * GetEnvVar opcode has reached opcode dispatch (there are no earlier errors). * - * This opcode uses: - * - register[0] as the output register. - * - mem_tag_reg[0] as the output register's memory tag - * - rop[1] as the enum value. - * - sel_opcode_error to indicate if the enum value is invalid. - * (which matches the instruction spec (PIL & Cpp)) + * This opcode also uses: + * - register[0]: the output register for the retrieved value (from execution.pil) + * - mem_tag_reg[0]: the output register's memory tag (from execution.pil) + * - rop[1]: the immediate enum value selecting which env var to read (from execution.pil) + * - contract_address: address of the current contract (from context.pil) + * - msg_sender: sender of the current call (from context.pil) + * - transaction_fee: the transaction fee (from context.pil) + * - is_static: whether the current context is static (from context.pil) + * - l2_gas_limit, l2_gas_used: L2 gas tracking (from context.pil) + * - da_gas_limit, da_gas_used: DA gas tracking (from context.pil) + * - sel_opcode_error: whether the enum value is invalid (from execution.pil) * - * For some env vars, the value will be available for access in the current execution/context/gas row: - * - ADDRESS - * - SENDER - * - TRANSACTIONFEE - * - ISSTATICCALL - * - L2GASLEFT - * - DAGASLEFT + * Interactions: + * - A precomputed table lookup maps the enum value to per-variable selectors, PI row + * indices, validity, and the output memory tag. This lookup is always performed + * (for all u8 enum values) to support error handling for invalid enums. + * - For global variables (chainId, version, blockNumber, timestamp, gasFees), a lookup + * into public_inputs retrieves the value. Two lookup variants exist (col0 and col1) + * because minFeePerL2Gas and minFeePerDaGas share a PI row across two columns. + * - For context variables (address, sender, transactionFee, isStaticCall), the value + * is read directly from context columns in the execution row. + * - For gas variables (l2GasLeft, daGasLeft), the value is computed as limit - used. * - * Other env vars come from global variables and must be looked up via public inputs. - * This virtual trace uses a selector to sel_envvar_pi_lookup to toggle the lookup from public inputs for: - * - CHAINID - * - VERSION - * - BLOCKNUMBER - * - TIMESTAMP - * - MINFEEPERL2GAS - * - MINFEEPERDAGAS + * Errors: + * - If the enum value is out of range (>=12), the precomputed table sets + * sel_opcode_error = 1 and all per-variable selectors to 0. * - * Note that the lookup to public inputs pulls 2 values (col0 and col1) - * because { minFeePerDaGas, minFeePerL2Gas } is in 1 row spanning 2 columns. - * This means that while nearly all globalVariables will come from public inputs col0, - * minFeePerL2Gas will come from col1. But for now, they share one lookup. + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_get_env_var). * - * If memberEnum is out of range, or if some other error occurs in an earlier temporality group - * (e.g. address resolution or out-of-gas errors), all of this virtual gadget's selectors should - * be 0 except for the precomputed table lookup, and output register & intermediate values should be 0. - * - * The PI lookup selector, PI row-index, misc selectors indicating which env var is being accessed, - * and a selector indicating whether the enum value is valid or out-of-range, are all looked up - * from a precomputed table (precomputed.pil). The lookup populates the corresponding (identically named) - * columns in this gadget: - * +-------+-----------------+-------------------+---------------------+--------------------+-------------------+------------+-----------+------------------+---------------+-----------------+--------------+----------------+-----------+ - * | Row | Env Variable | invalid_envvar_ | sel_envvar_pi_col0 | sel_envvar_pi_col1 | envvar_pi_row_idx | is_address | is_sender | is_transactionfee| is_feeperl2gas| is_isstaticcall | is_l2gasleft | is_dagasleft | out_tag | - * | (idx) | | enum | lookup | lookup | | | | | | | | | | - * +-------+-----------------+-------------------+---------------------+--------------------+-------------------+------------+-----------+------------------+---------------+-----------------+--------------+----------------+-----------+ - * | 0 | address | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | FF | - * | 1 | sender | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | FF | - * | 2 | transactionFee | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | FF | - * | 3 | chainId | 0 | 1 | 0 | CHAIN_ID_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | 0 | FF | - * | 4 | version | 0 | 1 | 0 | VERSION_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | 0 | FF | - * | 5 | blockNumber | 0 | 1 | 0 | BLOCK_NUM_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | 0 | U32 | - * | 6 | timestamp | 0 | 1 | 0 | TIMESTAMP_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | 0 | U64 | - * | 7 | minFeePerL2Gas | 0 | 0 | 1 | GAS_FEES_ROW_IDX | 0 | 0 | 0 | 1 | 0 | 0 | 0 | U128 | - * | 8 | minFeePerDaGas | 0 | 1 | 0 | GAS_FEES_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | 0 | u128 | - * | 9 | isStaticCall | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | U1 | - * | 10 | l2GasLeft | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | U32 | - * | 11 | daGasLeft | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | U32 | - * | 12+ | (invalid) | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | - * +-------+-----------------+-------------------+---------------------+--------------------+-------------------+------------+-----------+------------------+---------------+-----------------+--------------+----------------+-----------+ + * Precomputed table (from precomputed.pil): + * The PI lookup selector, PI row-index, per-variable selectors, validity flag, and output + * tag are all looked up from a precomputed table. The lookup populates the corresponding + * (identically named) columns in this gadget: + * +-----+----------------+----------------+--------------------+--------------------+-------------------+------------+-----------+-------------------+-----------------+--------------+--------------+---------+ + * | Row | Env Variable | invalid_envvar | sel_envvar_pi_col0 | sel_envvar_pi_col1 | envvar_pi_row_idx | is_address | is_sender | is_transactionfee | is_isstaticcall | is_l2gasleft | is_dagasleft | out_tag | + * +-----+----------------+----------------+--------------------+--------------------+-------------------+------------+-----------+-------------------+-----------------+--------------+--------------+---------+ + * | 0 | address | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | FF | + * | 1 | sender | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | FF | + * | 2 | transactionFee | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | FF | + * | 3 | chainId | 0 | 1 | 0 | CHAIN_ID_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | FF | + * | 4 | version | 0 | 1 | 0 | VERSION_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | FF | + * | 5 | blockNumber | 0 | 1 | 0 | BLOCK_NUM_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | U32 | + * | 6 | timestamp | 0 | 1 | 0 | TIMESTAMP_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | U64 | + * | 7 | minFeePerL2Gas | 0 | 0 | 1 | GAS_FEES_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | U128 | + * | 8 | minFeePerDaGas | 0 | 1 | 0 | GAS_FEES_ROW_IDX | 0 | 0 | 0 | 0 | 0 | 0 | U128 | + * | 9 | isStaticCall | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | U1 | + * | 10 | l2GasLeft | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | U32 | + * | 11 | daGasLeft | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | U32 | + * | 12+ | (invalid) | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | + * +-----+----------------+----------------+--------------------+--------------------+-------------------+------------+-----------+-------------------+-----------------+--------------+--------------+---------+ */ -namespace execution; // this is a virtual gadget that shares rows with the execution trace +namespace execution; // No relations will be checked if this identity is satisfied. #[skippable_if] @@ -81,7 +80,8 @@ namespace execution; // this is a virtual gadget that shares rows with the execu pol commit is_isstaticcall; pol commit is_l2gasleft; pol commit is_dagasleft; - // This lookup must be valid for all idx values up to max u8 (255) + + // This lookup must be valid for all idxs up to max u8 (255) // to allow error handling for any invalid enum that fits in a u8 immediate. // So, use the `precomputed.sel_range_8` as selector into the precomputed table. #[PRECOMPUTED_INFO] @@ -105,14 +105,24 @@ namespace execution; // this is a virtual gadget that shares rows with the execu // end columns from precomputed.pil's GETENVVAR opcode precomputed columns //////////////////////////////////////////////////////////////////////////// + // NOTE: we do not need to gate on `1 - sel_opcode_error` for these relations below + // because if `sel_execute_get_env_var` is 1 and `sel_opcode_error` is 1, then there is an invalid enum + // the precomputed table will provide 0 for the selectors like `sel_envvar_pi_lookup_col0/1`, + // `is_address`, `is_sender`,.. so the relations will be disabled anyway. + // In other words, `(sel_execute_get_env_var == 1 && is_address == 1)` implies `sel_opcode_error == 0`, + // and likewise for those other selectors. + + // Unconstrained if sel_envvar_pi_lookup_col0=0 and sel_envvar_pi_lookup_col1=0. pol commit value_from_pi; - // Public input lookup for global variables. Usually just read from PI col0. + + // Public input lookups for global variables. Usually just read from PI col0 #[READ_FROM_PUBLIC_INPUTS_COL0] sel_envvar_pi_lookup_col0 { envvar_pi_row_idx, value_from_pi } in public_inputs.sel { precomputed.idx, public_inputs.cols[0] }; + // Read from PI col1 instead for minFeePerL2Gas #[READ_FROM_PUBLIC_INPUTS_COL1] sel_envvar_pi_lookup_col1 { @@ -121,13 +131,6 @@ namespace execution; // this is a virtual gadget that shares rows with the execu precomputed.idx, public_inputs.cols[1] }; - // NOTE: we do not need to gate on `1 - sel_opcode_error` for these register-setting relations below - // because if `sel_execute_get_env_var` is 1 and `sel_opcode_error` is 0, then there is an invalid enum - // the precomputed table will provide 0 for the selectors like `sel_envvar_pi_lookup_col0/1`, - // `is_address`, `is_sender`,.. so the relations will be disabled anyway. - // In other words, `(sel_execute_get_env_var == 1 && is_address == 1)` implies `sel_opcode_error == 0`, - // and likewise for those other selectors. - // Set output register to the value from public inputs #[FROM_PUBLIC_INPUTS] // if sel_envvar_pi_lookup_col0 == 1 or sel_envvar_pi_lookup_col1 == 1, then value_from_pi must match the value from public inputs diff --git a/barretenberg/cpp/pil/vm2/opcodes/l1_to_l2_message_exists.pil b/barretenberg/cpp/pil/vm2/opcodes/l1_to_l2_message_exists.pil index 4657af974235..4960844d7a65 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/l1_to_l2_message_exists.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/l1_to_l2_message_exists.pil @@ -1,19 +1,36 @@ include "../constants_gen.pil"; -include "../range_check.pil"; +include "../gt.pil"; include "../trees/l1_to_l2_message_tree_check.pil"; -// L1_TO_L2_MESSAGE_EXISTS opcode: Checks if a l1 to l2 message exists in the l1 to l2 message tree at a given leaf index -// -// Register usage: -// - register[0]: Contains the msg_hash to check for existence -// - register[1]: Contains the leaf_index where the msg_hash is going to be checked -// - register[2]: Boolean output of the opcode indicating whether the msg_hash exists at the given leaf index -// -// The opcode leverages the l1_to_l2_message_tree_check gadget to verify the existence check operation -// against the current l1 to l2 message tree root. The opcode will write false if the -// leaf index is greater than or equal to the total number of leaves in the l1 to l2 message tree. -// -namespace execution; // this is a virtual gadget that shares rows with the execution trace +/** + * This virtual gadget implements the L1ToL2MessageExists opcode, which checks whether + * a given L1-to-L2 message hash exists at a specified leaf index in the L1-to-L2 message tree. + * The result is a boolean written to the output register. + * + * The opcode is gated by the `sel_execute_l1_to_l2_message_exists` selector, which is set to 1 + * if the L1ToL2MessageExists opcode has reached opcode dispatch (there are no earlier errors). + * + * This opcode also uses: + * - register[0]: the msg_hash to check for existence (FF) (from execution.pil) + * - register[1]: the leaf_index where the msg_hash should be checked (U64) (from execution.pil) + * - register[2]: boolean output indicating whether the msg_hash exists (U1) (from execution.pil) + * - l1_l2_tree_root: the L1-to-L2 message tree root (from context.pil) + * + * Interactions: + * - A lookup into the gt gadget checks whether the leaf_index is < 2**L1_TO_L2_MSG_TREE_HEIGHT + * (less than L1_TO_L2_MSG_TREE_LEAF_COUNT). The result is stored in l1_to_l2_msg_leaf_in_range. + * - If the leaf index is in range, a lookup into l1_to_l2_message_tree_check verifies + * whether the msg_hash exists at the given leaf_index against the current tree root. + * - If the leaf index is out of range, the output register is forced to false (0). + * + * Errors: + * - This opcode is infallible and sel_opcode_error is constrained to be zero + * in execution.pil (see #[INFALLIBLE_OPCODES_SUCCESS]). + * + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_l1_to_l2_message_exists). + */ +namespace execution; #[skippable_if] sel_execute_l1_to_l2_message_exists = 0; // from execution.pil. @@ -21,7 +38,7 @@ namespace execution; // this is a virtual gadget that shares rows with the execu // Constrained to be boolean by the lookup into gt. (provided that sel_execute_l1_to_l2_message_exists == 1). pol commit l1_to_l2_msg_leaf_in_range; - // TODO: We need this temporarily while we do not allow for aliases in the lookup tuple + // Lookup constant support: We need this temporarily while we do not allow for aliases in the lookup tuple pol commit l1_to_l2_msg_tree_leaf_count; sel_execute_l1_to_l2_message_exists * (l1_to_l2_msg_tree_leaf_count - constants.L1_TO_L2_MSG_TREE_LEAF_COUNT) = 0; @@ -60,5 +77,4 @@ namespace execution; // this is a virtual gadget that shares rows with the execu #[L1_TO_L2_MSG_EXISTS_U1_OUTPUT_TAG] sel_execute_l1_to_l2_message_exists * (constants.MEM_TAG_U1 - mem_tag_reg[2]) = 0; - // This opcode is infallible and sel_opcode_error is constrained to be zero - // in execution.pil (see #[INFALLIBLE_OPCODES_SUCCESS]). + // Infallible opcode: sel_opcode_error constrained to 0 (see #[INFALLIBLE_OPCODES_SUCCESS]). diff --git a/barretenberg/cpp/pil/vm2/opcodes/notehash_exists.pil b/barretenberg/cpp/pil/vm2/opcodes/notehash_exists.pil index c867c270b0ad..bba90cdc1d3f 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/notehash_exists.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/notehash_exists.pil @@ -1,20 +1,36 @@ include "../constants_gen.pil"; -include "../range_check.pil"; include "../trees/note_hash_tree_check.pil"; include "../gt.pil"; -// NOTEHASH_EXISTS opcode: Checks if a note hash exists in the note hash tree at a given leaf index -// -// Register usage: -// - register[0]: Contains the unique_note_hash to check for existence -// - register[1]: Contains the leaf_index where the unique_note_hash is going to be checked -// - register[2]: Boolean output of the opcode indicating whether the note hash exists at the given leaf index -// -// The opcode leverages the note_hash_tree_check gadget to verify the existence check operation -// against the current note hash tree root. The opcode will write false if the -// leaf index is greater than or equal to the total number of leaves in the note hash tree. -// -namespace execution; // this is a virtual gadget that shares rows with the execution trace +/** + * This virtual gadget implements the NoteHashExists opcode, which checks whether + * a given unique_note_hash exists at a specified leaf index in the note hash tree. + * The result is a boolean written to the output register. + * + * The opcode is gated by the `sel_execute_notehash_exists` selector, which is set to 1 + * if the NoteHashExists opcode has reached opcode dispatch (there are no earlier errors). + * + * This opcode also uses: + * - register[0]: the unique_note_hash to check for existence (FF) (from execution.pil) + * - register[1]: the leaf_index where the unique_note_hash should be checked (U64) (from execution.pil) + * - register[2]: boolean output indicating whether the note hash exists (U1) (from execution.pil) + * - prev_note_hash_tree_root: the note hash tree root (from context.pil) + * + * Interactions: + * - A lookup into the gt gadget checks whether the leaf_index is < 2**NOTE_HASH_TREE_HEIGHT + * (less than NOTE_HASH_TREE_LEAF_COUNT). The result is stored in note_hash_leaf_in_range. + * - If the leaf index is in range, a lookup into note_hash_tree_check verifies + * whether the unique_note_hash exists at the given leaf_index against the current tree root. + * - If the leaf index is out of range, the output register is forced to false (0). + * + * Errors: + * - This opcode is infallible and sel_opcode_error is constrained to be zero + * in execution.pil (see #[INFALLIBLE_OPCODES_SUCCESS]). + * + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_notehash_exists). + */ +namespace execution; #[skippable_if] sel_execute_notehash_exists = 0; // from execution.pil. @@ -22,7 +38,7 @@ namespace execution; // this is a virtual gadget that shares rows with the execu // Constrained to be boolean by the lookup into gt. (provided that sel_execute_notehash_exists == 1). pol commit note_hash_leaf_in_range; - // TODO: We need this temporarily while we do not allow for aliases in the lookup tuple + // Lookup constant support: We need this temporarily while we do not allow for aliases in the lookup tuple pol commit note_hash_tree_leaf_count; sel_execute_notehash_exists * (note_hash_tree_leaf_count - constants.NOTE_HASH_TREE_LEAF_COUNT) = 0; @@ -63,5 +79,4 @@ namespace execution; // this is a virtual gadget that shares rows with the execu #[NOTEHASH_EXISTS_U1_OUTPUT_TAG] sel_execute_notehash_exists * (constants.MEM_TAG_U1 - mem_tag_reg[2]) = 0; - // This opcode is infallible and sel_opcode_error is constrained to be zero - // in execution.pil (see #[INFALLIBLE_OPCODES_SUCCESS]). + // Infallible opcode: sel_opcode_error constrained to 0 (see #[INFALLIBLE_OPCODES_SUCCESS]). diff --git a/barretenberg/cpp/pil/vm2/opcodes/nullifier_exists.pil b/barretenberg/cpp/pil/vm2/opcodes/nullifier_exists.pil index d81d96a4aa53..caa2f45bfd00 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/nullifier_exists.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/nullifier_exists.pil @@ -1,24 +1,29 @@ include "../constants_gen.pil"; - /** - * This virtual gadget implements the NullifierExists opcode, which checks if a siloed nullifier exists - * in the nullifier tree. +/** + * This virtual gadget implements the NullifierExists opcode, which checks whether + * a given siloed nullifier exists in the nullifier tree. + * The result is a boolean written to the output register. * - * The opcode is gated by the `sel_execute_nullifier_exists` selector, which is set to 1 if the - * NullifierExists opcode has reached dispatch (there are no earlier errors). + * The opcode is gated by the `sel_execute_nullifier_exists` selector, which is set to 1 + * if the NullifierExists opcode has reached opcode dispatch (there are no earlier errors). * - * This opcode uses: - * - register[0] as the siloed nullifier input register (FF) - * - register[1] as the output register (boolean result to be tagged as u1) + * This opcode also uses: + * - register[0]: the siloed nullifier to check for existence (FF) (from execution.pil) + * - register[1]: boolean output indicating whether the nullifier exists (U1) (from execution.pil) + * - prev_nullifier_tree_root: the nullifier tree root (from context.pil) * - * Memory reads and writes are handled by standard execution logic. + * Interactions: + * - A lookup into indexed_tree_check determines whether the siloed nullifier exists + * in the nullifier tree. The nullifier is already siloed by the caller (no siloing + * happens in this opcode, sel_silo = 0). * - * The gadget performs a lookup into the indexed_tree_check gadget to determine if the nullifier - * exists. The nullifier is already siloed by the caller (no siloing happens in this opcode). - * The result is written to the output register. + * Errors: + * - This opcode is infallible and sel_opcode_error is constrained to be zero + * in execution.pil (see #[INFALLIBLE_OPCODES_SUCCESS]). * - * If there are errors in earlier temporality groups (e.g. address resolution or out-of-gas errors), - * all selectors should be 0 and output register & intermediate values should be 0. + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_nullifier_exists). */ namespace execution; // this is a virtual gadget that shares rows with the execution trace diff --git a/barretenberg/cpp/pil/vm2/opcodes/send_l2_to_l1_msg.pil b/barretenberg/cpp/pil/vm2/opcodes/send_l2_to_l1_msg.pil index 243d90972f0b..49fff84f50d0 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/send_l2_to_l1_msg.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/send_l2_to_l1_msg.pil @@ -2,25 +2,39 @@ include "../constants_gen.pil"; include "../public_inputs.pil"; /** - * This virtual gadget implements the SendL2ToL1Msg opcode, which attempts to send a message - * from L2 to L1. The message is written to the public inputs for later processing by the - * L1 contract. + * This virtual gadget implements the SendL2ToL1Msg opcode, which sends a message + * from L2 to L1. The message (recipient + content + sender contract address) is written + * to the public inputs for later processing by the L1 contract. It also increments the + * number of L2-to-L1 messages emitted. * + * The opcode is gated by the `sel_execute_send_l2_to_l1_msg` selector, which is set to 1 if the + * SendL2ToL1Msg opcode has reached opcode dispatch (there are no earlier errors). * - * This opcode uses: - * - register[0] as the recipient address register (FF) - * - register[1] as the message content register (FF) + * This opcode also uses: + * - register[0]: the recipient address register (FF) (from execution.pil) + * - register[1]: the message content register (FF) (from execution.pil) + * - is_static: whether the current context is static (from context.pil) + * - contract_address: address of the current contract (from context.pil) + * - prev_num_l2_to_l1_messages: count before this emission (from context.pil) + * - num_l2_to_l1_messages: count after this emission (from context.pil) + * - sel_opcode_error: whether an opcode error was raised (from execution.pil) + * - discard: whether the transaction will be discarded (reverted) (from discard.pil) * * Interactions: + * - The gadget performs a lookup into the ff_gt gadget to validate the recipient is a valid + * Ethereum address (not larger than MAX_ETH_ADDRESS_VALUE). * - The gadget performs a lookup into the public_inputs gadget to write the L2-to-L1 message. - * - This lookup is only performed if no error occurs. + * - This lookup is only performed if no error occurs and discard is off (sel_write_l2_to_l1_msg = 1). * * Errors: * - If the max L2-to-L1 message writes limit has been reached, the operation raises an opcode error. - * - If the operation is executed in a static context, the operation raises an opcode error. + * - If the current context is static, the operation raises an opcode error. + * - If the recipient address exceeds MAX_ETH_ADDRESS_VALUE, the operation raises an opcode error. * + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_send_l2_to_l1_msg). */ -namespace execution; // this is a virtual gadget that shares rows with the execution trace +namespace execution; // No relations will be checked if this identity is satisfied. #[skippable_if] @@ -31,10 +45,10 @@ namespace execution; // this is a virtual gadget that shares rows with the execu pol REMAINING_L2_TO_L1_MSG_WRITES = constants.MAX_L2_TO_L1_MSGS_PER_TX - prev_num_l2_to_l1_messages; - pol commit sel_l2_to_l1_msg_limit_error; + pol commit sel_l2_to_l1_msg_limit_error; // @boolean sel_l2_to_l1_msg_limit_error * (1 - sel_l2_to_l1_msg_limit_error) = 0; - pol commit remaining_l2_to_l1_msgs_inv; + pol commit remaining_l2_to_l1_msgs_inv; // @zero-check // Limit error if REMAINING_L2_TO_L1_MSG_WRITES is 0 #[MAX_WRITES_REACHED] sel_execute_send_l2_to_l1_msg * (REMAINING_L2_TO_L1_MSG_WRITES * (sel_l2_to_l1_msg_limit_error * (1 - remaining_l2_to_l1_msgs_inv) + remaining_l2_to_l1_msgs_inv) - 1 + sel_l2_to_l1_msg_limit_error) = 0; diff --git a/barretenberg/cpp/pil/vm2/opcodes/sload.pil b/barretenberg/cpp/pil/vm2/opcodes/sload.pil index 6af26e69bf0d..4d811cf9bf4c 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/sload.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/sload.pil @@ -1,24 +1,30 @@ include "../constants_gen.pil"; include "../trees/public_data_check.pil"; -// SLOAD opcode: Reads a value from storage at the provided slot -// -// This opcode interacts with the public data check gadget to read a value at the specified slot. -// The execution trace reads the slot from memory into register[0], the contract address from -// memory into register[1], performs FF tag validation, and writes the value retrieved from the -// tree to memory via register[2]. -// The value written to memory is checked to have tag FF. -// -// Register usage: -// - register[0]: Contains the storage slot to read from -// - register[1]: Contains the contract address to read from -// - register[2]: Contains the retrieved value to be written to memory -// -// The opcode leverages the public_data_check gadget to verify the storage read operation -// against the current public data tree root. The public_data_check gadget silos the slot -// with the contract address. -// -namespace execution; // this is a virtual gadget that shares rows with the execution trace +/** + * This virtual gadget implements the SLOAD opcode, which reads a value from public storage + * at the provided slot. The slot is siloed with the contract address by the public_data_check + * gadget before the read. The value retrieved from the tree is written to memory with tag FF. + * + * The opcode is gated by the `sel_execute_sload` selector, which is set to 1 if the + * SLOAD opcode has reached opcode dispatch (there are no earlier errors). + * + * This opcode also uses: + * - register[0]: the storage slot to read from (FF) (from execution.pil) + * - register[1]: the contract address to read from (FF) (from execution.pil) + * - register[2]: the retrieved value to be written to memory (FF) (from execution.pil) + * - prev_public_data_tree_root: root of the public data tree (from context.pil) + * + * Interactions: + * - The gadget performs a lookup into the public_data_check gadget to read the storage value. + * - The lookup passes the slot, contract address, and tree root, and receives the value. + * - The public_data_check gadget silos the slot with the contract address. + * + * This opcode is infallible and sel_opcode_error is constrained to be zero + * in execution.pil (see #[INFALLIBLE_OPCODES_SUCCESS]). + * + */ +namespace execution; #[skippable_if] sel_execute_sload = 0; // from execution.pil. diff --git a/barretenberg/cpp/pil/vm2/opcodes/sstore.pil b/barretenberg/cpp/pil/vm2/opcodes/sstore.pil index 111fc75946bf..bd0978a6d30a 100644 --- a/barretenberg/cpp/pil/vm2/opcodes/sstore.pil +++ b/barretenberg/cpp/pil/vm2/opcodes/sstore.pil @@ -2,28 +2,58 @@ include "../constants_gen.pil"; include "../trees/public_data_check.pil"; include "../trees/indexed_tree_check.pil"; -// SSTORE opcode: Writes a value to storage at the provided slot -// -// This opcode interacts with the public data check gadget to write a value at the specified slot. -// The execution trace reads the slot and value from memory into register[1] and register[0] respectively, -// performs dynamic gas calculation based on whether the slot was previously written to, -// validates against maximum data write limits, and updates the public data tree. -// -// Register usage: -// - register[0]: Contains the value to write to storage -// - register[1]: Contains the storage slot to write to -// -// The opcode leverages the public_data_check gadget to verify the storage write operation -// and the indexed_tree_check gadget to track written slots for gas calculation. -// -namespace execution; // this is a virtual gadget that shares rows with the execution trace +/** + * This virtual gadget implements the SStore opcode, which writes a value to storage + * at the provided slot. The slot is recorded in the written public data slots tree + * (via indexed_tree_check) for dynamic gas tracking, and the value is written to the public + * data tree (via public_data_check). + * + * The opcode is gated by the `sel_execute_sstore` selector, which is set to 1 if the + * SStore opcode has reached opcode dispatch (there are no earlier errors). + * + * This opcode also uses: + * - register[0]: the value to write to storage (FF) (from execution.pil) + * - register[1]: the storage slot to write to (FF) (from execution.pil) + * - is_static: whether the current context is static (from context.pil) + * - contract_address: address of the current contract (from context.pil) + * - prev_written_public_data_slots_tree_root: root before the slot record (from context.pil) + * - prev_written_public_data_slots_tree_size: size before the slot record (from context.pil) + * - written_public_data_slots_tree_root: root after the slot record (from context.pil) + * - written_public_data_slots_tree_size: size after the slot record (from context.pil) + * - written_slots_tree_height: height of the written slots tree (from execution.pil) + * - written_slots_tree_siloing_separator: siloing separator for written slots (from execution.pil) + * - prev_public_data_tree_root: root before the write (from context.pil) + * - prev_public_data_tree_size: size before the write (from context.pil) + * - public_data_tree_root: root after the write (from context.pil) + * - public_data_tree_size: size after the write (from context.pil) + * - dynamic_da_gas_factor: 1 if slot not previously written, 0 otherwise (from execution.pil) + * - clk: clock value for ordering (from execution.pil) + * - sel_opcode_error: whether an opcode error was raised (from execution.pil) + * - discard: whether the transaction will be discarded (reverted) (from discard.pil) + * + * Interactions: + * - The gadget performs a lookup into the indexed_tree_check gadget to record the written slot + * (for dynamic gas tracking). The lookup passes sel_silo=1 so the tree check gadget silos + * the slot with the contract address. + * - The gadget performs a lookup into the public_data_check gadget to write the value at the slot. + * - Both lookups are only performed if no error occurs (sel_write_public_data = 1). + * + * Errors: + * - If the max data writes limit has been reached and the slot was not previously written + * (dynamic_da_gas_factor = 1), the operation raises an opcode error. + * - If the current context is static, the operation raises an opcode error. + * + * If there are errors in earlier temporality groups (i.e. the opcode is not dispatched), + * all columns in this trace should be 0 (gated by sel_execute_sstore). + */ +namespace execution; #[skippable_if] sel_execute_sstore = 0; // from execution.pil. // =========== VALIDATION =========== - pol commit max_data_writes_reached; + pol commit max_data_writes_reached; // @boolean max_data_writes_reached * (1 - max_data_writes_reached) = 0; // We use the written public data slots tree size (taking into account the prefill) to track how many data writes we have emitted. @@ -31,7 +61,7 @@ namespace execution; // this is a virtual gadget that shares rows with the execu constants.AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_INITIAL_SIZE - prev_written_public_data_slots_tree_size; - pol commit remaining_data_writes_inv; + pol commit remaining_data_writes_inv; // @zero-check #[SSTORE_MAX_DATA_WRITES_REACHED] sel_execute_sstore * (REMAINING_DATA_WRITES * (max_data_writes_reached * (1 - remaining_data_writes_inv) + remaining_data_writes_inv) - 1 + max_data_writes_reached) = 0; @@ -43,13 +73,13 @@ namespace execution; // this is a virtual gadget that shares rows with the execu #[OPCODE_ERROR_IF_OVERFLOW_OR_STATIC] sel_execute_sstore * ((1 - max_data_writes_reached * dynamic_da_gas_factor) * (1 - is_static) - (1 - sel_opcode_error)) = 0; - // Commited since it's used in the lookup + // Committed since it's used in the lookup // Note: we could perform the work unconditionally here, since the roots will be reverted if sel_opcode_error is one. // We'd save one column, but we'd perform more work in the error case, both in simulation and proving. - pol commit sel_write_public_data; + pol commit sel_write_public_data; // @boolean (by definition) #[SEL_WRITE_PUBLIC_DATA_IS_EXECUTE_AND_NOT_ERROR] sel_write_public_data = sel_execute_sstore * (1 - sel_opcode_error); - // Note that this forces sel_write_public_data to 0 when sel_execute_store is 0 (prevents ghost row injection attacks) + // Note that this forces sel_write_public_data to 0 when sel_execute_sstore is 0 (prevents ghost row injection attacks) // =========== OPCODE EXECUTION =========== diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp index a2256ccb5d7f..3494e01e92e0 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/execution_trace.cpp @@ -614,10 +614,10 @@ void ExecutionTraceBuilder::process( trace.set(row, { { - { C::execution_max_data_writes_reached, remaining_data_writes == 0 }, + { C::execution_max_data_writes_reached, (remaining_data_writes == 0) ? 1 : 0 }, { C::execution_remaining_data_writes_inv, remaining_data_writes }, // Will be inverted in batch later. - { C::execution_sel_write_public_data, !opcode_execution_failed }, + { C::execution_sel_write_public_data, opcode_execution_failed ? 0 : 1 }, { C::execution_written_slots_tree_height, AVM_WRITTEN_PUBLIC_DATA_SLOTS_TREE_HEIGHT }, { C::execution_written_slots_tree_siloing_separator, DOM_SEP__PUBLIC_LEAF_SLOT }, } }); @@ -628,7 +628,7 @@ void ExecutionTraceBuilder::process( trace.set(row, { { - { C::execution_note_hash_leaf_in_range, note_hash_leaf_in_range }, + { C::execution_note_hash_leaf_in_range, note_hash_leaf_in_range ? 1 : 0 }, { C::execution_note_hash_tree_leaf_count, FF(note_hash_tree_leaf_count) }, } }); } else if (*exec_opcode == ExecutionOpCode::EMITNOTEHASH) { @@ -637,10 +637,10 @@ void ExecutionTraceBuilder::process( trace.set(row, { { - { C::execution_sel_reached_max_note_hashes, remaining_note_hashes == 0 }, + { C::execution_sel_reached_max_note_hashes, (remaining_note_hashes == 0) ? 1 : 0 }, { C::execution_remaining_note_hashes_inv, remaining_note_hashes }, // Will be inverted in batch later. - { C::execution_sel_write_note_hash, !opcode_execution_failed }, + { C::execution_sel_write_note_hash, opcode_execution_failed ? 0 : 1 }, } }); } else if (*exec_opcode == ExecutionOpCode::L1TOL2MSGEXISTS) { uint64_t leaf_index = registers[1].as(); @@ -649,7 +649,7 @@ void ExecutionTraceBuilder::process( trace.set(row, { { - { C::execution_l1_to_l2_msg_leaf_in_range, l1_to_l2_msg_leaf_in_range }, + { C::execution_l1_to_l2_msg_leaf_in_range, l1_to_l2_msg_leaf_in_range ? 1 : 0 }, { C::execution_l1_to_l2_msg_tree_leaf_count, FF(l1_to_l2_msg_tree_leaf_count) }, } }); } else if (exec_opcode == ExecutionOpCode::NULLIFIEREXISTS) { @@ -662,11 +662,11 @@ void ExecutionTraceBuilder::process( MAX_NULLIFIERS_PER_TX - ex_event.before_context_event.tree_states.nullifier_tree.counter; trace.set(row, - { { { C::execution_sel_reached_max_nullifiers, remaining_nullifiers == 0 }, + { { { C::execution_sel_reached_max_nullifiers, (remaining_nullifiers == 0) ? 1 : 0 }, { C::execution_remaining_nullifiers_inv, remaining_nullifiers }, // Will be inverted in batch later. { C::execution_sel_write_nullifier, - remaining_nullifiers != 0 && !ex_event.before_context_event.is_static }, + (remaining_nullifiers != 0 && !ex_event.before_context_event.is_static) ? 1 : 0 }, { C::execution_nullifier_pi_offset, AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_NULLIFIERS_ROW_IDX + ex_event.before_context_event.tree_states.nullifier_tree.counter }, @@ -680,18 +680,19 @@ void ExecutionTraceBuilder::process( bool sel_too_large_recipient_error = static_cast(recipient) > static_cast(MAX_ETH_ADDRESS_VALUE); - trace.set(row, - { { { C::execution_sel_l2_to_l1_msg_limit_error, remaining_l2_to_l1_msgs == 0 }, - { C::execution_remaining_l2_to_l1_msgs_inv, - remaining_l2_to_l1_msgs }, // Will be inverted in batch later. - { C::execution_max_eth_address_value, FF(MAX_ETH_ADDRESS_VALUE) }, - { C::execution_sel_too_large_recipient_error, sel_too_large_recipient_error }, - { C::execution_sel_write_l2_to_l1_msg, !opcode_execution_failed && !is_discarding() }, - { - C::execution_public_inputs_index, - AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX + - ex_event.before_context_event.numL2ToL1Messages, - } } }); + trace.set( + row, + { { { C::execution_sel_l2_to_l1_msg_limit_error, (remaining_l2_to_l1_msgs == 0) ? 1 : 0 }, + { C::execution_remaining_l2_to_l1_msgs_inv, + remaining_l2_to_l1_msgs }, // Will be inverted in batch later. + { C::execution_max_eth_address_value, FF(MAX_ETH_ADDRESS_VALUE) }, + { C::execution_sel_too_large_recipient_error, sel_too_large_recipient_error ? 1 : 0 }, + { C::execution_sel_write_l2_to_l1_msg, (!opcode_execution_failed && !is_discarding()) ? 1 : 0 }, + { + C::execution_public_inputs_index, + AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX + + ex_event.before_context_event.numL2ToL1Messages, + } } }); } } @@ -1027,7 +1028,7 @@ void ExecutionTraceBuilder::invert_columns(TraceContainer& trace) C::execution_remaining_data_writes_inv, C::execution_remaining_note_hashes_inv, C::execution_remaining_nullifiers_inv, - // L1ToL2MsgExists. + // SendL2ToL1Msg. C::execution_remaining_l2_to_l1_msgs_inv, // Discard. C::execution_dying_context_id_inv, diff --git a/yarn-project/simulator/docs/avm/opcodes/getenvvar.md b/yarn-project/simulator/docs/avm/opcodes/getenvvar.md index 80fd823e92cb..ef05fafdcb6d 100644 --- a/yarn-project/simulator/docs/avm/opcodes/getenvvar.md +++ b/yarn-project/simulator/docs/avm/opcodes/getenvvar.md @@ -95,7 +95,9 @@ packet-beta ## Tag Updates -- `T[dstOffset] = FIELD` +The destination tag is set to the type of the requested variable (see Variable Reference table above): + +- `T[dstOffset] = Type(varEnum)` ## Error Conditions