diff --git a/avm-transpiler/src/opcodes.rs b/avm-transpiler/src/opcodes.rs
index 2b63c8e987e..206325cfeff 100644
--- a/avm-transpiler/src/opcodes.rs
+++ b/avm-transpiler/src/opcodes.rs
@@ -69,6 +69,8 @@ pub enum AvmOpcode {
POSEIDON2,
SHA256, // temp - may be removed, but alot of contracts rely on it
PEDERSEN, // temp - may be removed, but alot of contracts rely on it
+ // Conversions
+ TORADIXLE,
}
impl AvmOpcode {
@@ -155,6 +157,8 @@ impl AvmOpcode {
AvmOpcode::POSEIDON2 => "POSEIDON2",
AvmOpcode::SHA256 => "SHA256 ",
AvmOpcode::PEDERSEN => "PEDERSEN",
+ // Conversions
+ AvmOpcode::TORADIXLE => "TORADIXLE",
}
}
}
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 2a4dd1138e9..21423838f43 100644
--- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp
+++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp
@@ -96,6 +96,9 @@ enum class OpCode : uint8_t {
KECCAK,
POSEIDON2,
+ // Conversions
+ TORADIXLE,
+
// Sentinel
LAST_OPCODE_SENTINEL,
};
diff --git a/docs/docs/protocol-specs/public-vm/gen/_instruction-set.mdx b/docs/docs/protocol-specs/public-vm/gen/_instruction-set.mdx
index a13fb19a0f5..1c275e77e92 100644
--- a/docs/docs/protocol-specs/public-vm/gen/_instruction-set.mdx
+++ b/docs/docs/protocol-specs/public-vm/gen/_instruction-set.mdx
@@ -154,98 +154,105 @@ Click on an instruction name to jump to its section.
}
- 0x14 | [`CONTRACTCALLDEPTH`](#isa-section-contractcalldepth) |
+ 0x14 | [`TRANSACTIONFEE`](#isa-section-transactionfee) |
+ Get the computed transaction fee during teardown phase, zero otherwise |
+ {
+ `M[dstOffset] = context.environment.transactionFee`
+ } |
+
+
+ 0x15 | [`CONTRACTCALLDEPTH`](#isa-section-contractcalldepth) |
Get how many contract calls deep the current call context is |
{
`M[dstOffset] = context.environment.contractCallDepth`
} |
- 0x15 | [`CHAINID`](#isa-section-chainid) |
+ 0x16 | [`CHAINID`](#isa-section-chainid) |
Get this rollup's L1 chain ID |
{
`M[dstOffset] = context.environment.globals.chainId`
} |
- 0x16 | [`VERSION`](#isa-section-version) |
+ 0x17 | [`VERSION`](#isa-section-version) |
Get this rollup's L2 version ID |
{
`M[dstOffset] = context.environment.globals.version`
} |
- 0x17 | [`BLOCKNUMBER`](#isa-section-blocknumber) |
+ 0x18 | [`BLOCKNUMBER`](#isa-section-blocknumber) |
Get this L2 block's number |
{
`M[dstOffset] = context.environment.globals.blocknumber`
} |
- 0x18 | [`TIMESTAMP`](#isa-section-timestamp) |
+ 0x19 | [`TIMESTAMP`](#isa-section-timestamp) |
Get this L2 block's timestamp |
{
`M[dstOffset] = context.environment.globals.timestamp`
} |
- 0x19 | [`COINBASE`](#isa-section-coinbase) |
+ 0x1a | [`COINBASE`](#isa-section-coinbase) |
Get the block's beneficiary address |
{
`M[dstOffset] = context.environment.globals.coinbase`
} |
- 0x1a | [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) |
+ 0x1b | [`BLOCKL2GASLIMIT`](#isa-section-blockl2gaslimit) |
Total amount of "L2 gas" that a block can consume |
{
`M[dstOffset] = context.environment.globals.l2GasLimit`
} |
- 0x1b | [`BLOCKDAGASLIMIT`](#isa-section-blockdagaslimit) |
+ 0x1c | [`BLOCKDAGASLIMIT`](#isa-section-blockdagaslimit) |
Total amount of "DA gas" that a block can consume |
{
`M[dstOffset] = context.environment.globals.daGasLimit`
} |
- 0x1c | [`CALLDATACOPY`](#isa-section-calldatacopy) |
+ 0x1d | [`CALLDATACOPY`](#isa-section-calldatacopy) |
Copy calldata into memory |
{
`M[dstOffset:dstOffset+copySize] = context.environment.calldata[cdOffset:cdOffset+copySize]`
} |
- 0x1d | [`L2GASLEFT`](#isa-section-l2gasleft) |
+ 0x1e | [`L2GASLEFT`](#isa-section-l2gasleft) |
Remaining "L2 gas" for this call (after this instruction) |
{
`M[dstOffset] = context.MachineState.l2GasLeft`
} |
- 0x1e | [`DAGASLEFT`](#isa-section-dagasleft) |
+ 0x1f | [`DAGASLEFT`](#isa-section-dagasleft) |
Remaining "DA gas" for this call (after this instruction) |
{
`M[dstOffset] = context.machineState.daGasLeft`
} |
- 0x1f | [`JUMP`](#isa-section-jump) |
+ 0x20 | [`JUMP`](#isa-section-jump) |
Jump to a location in the bytecode |
{
`context.machineState.pc = loc`
} |
- 0x20 | [`JUMPI`](#isa-section-jumpi) |
+ 0x21 | [`JUMPI`](#isa-section-jumpi) |
Conditionally jump to a location in the bytecode |
{
`context.machineState.pc = M[condOffset] > 0 ? loc : context.machineState.pc`
} |
- 0x21 | [`INTERNALCALL`](#isa-section-internalcall) |
+ 0x22 | [`INTERNALCALL`](#isa-section-internalcall) |
Make an internal call. Push the current PC to the internal call stack and jump to the target location. |
{`context.machineState.internalCallStack.push(context.machineState.pc)
@@ -253,49 +260,49 @@ context.machineState.pc = loc`}
|
- 0x22 | [`INTERNALRETURN`](#isa-section-internalreturn) |
+ 0x23 | [`INTERNALRETURN`](#isa-section-internalreturn) |
Return from an internal call. Pop from the internal call stack and jump to the popped location. |
{
`context.machineState.pc = context.machineState.internalCallStack.pop()`
} |
- 0x23 | [`SET`](#isa-section-set) |
+ 0x24 | [`SET`](#isa-section-set) |
Set a memory word from a constant in the bytecode |
{
`M[dstOffset] = const`
} |
- 0x24 | [`MOV`](#isa-section-mov) |
+ 0x25 | [`MOV`](#isa-section-mov) |
Move a word from source memory location to destination |
{
`M[dstOffset] = M[srcOffset]`
} |
- 0x25 | [`CMOV`](#isa-section-cmov) |
+ 0x26 | [`CMOV`](#isa-section-cmov) |
Move a word (conditionally chosen) from one memory location to another (`d = cond > 0 ? a : b`) |
{
`M[dstOffset] = M[condOffset] > 0 ? M[aOffset] : M[bOffset]`
} |
- 0x26 | [`SLOAD`](#isa-section-sload) |
+ 0x27 | [`SLOAD`](#isa-section-sload) |
Load a word from this contract's persistent public storage. Zero is loaded for unwritten slots. |
{`M[dstOffset] = S[M[slotOffset]]`}
|
- 0x27 | [`SSTORE`](#isa-section-sstore) |
+ 0x28 | [`SSTORE`](#isa-section-sstore) |
Write a word to this contract's persistent public storage |
{`S[M[slotOffset]] = M[srcOffset]`}
|
- 0x28 | [`NOTEHASHEXISTS`](#isa-section-notehashexists) |
+ 0x29 | [`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({
@@ -306,7 +313,7 @@ M[existsOffset] = exists`}
|
- 0x29 | [`EMITNOTEHASH`](#isa-section-emitnotehash) |
+ 0x2a | [`EMITNOTEHASH`](#isa-section-emitnotehash) |
Emit a new note hash to be inserted into the note hash tree |
{`context.worldState.noteHashes.append(
@@ -315,7 +322,7 @@ M[existsOffset] = exists`}
|
- 0x2a | [`NULLIFIEREXISTS`](#isa-section-nullifierexists) |
+ 0x2b | [`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 = pendingNullifiers.has(M[addressOffset], M[nullifierOffset]) || context.worldState.nullifiers.has(
@@ -325,7 +332,7 @@ M[existsOffset] = exists`}
|
- 0x2b | [`EMITNULLIFIER`](#isa-section-emitnullifier) |
+ 0x2c | [`EMITNULLIFIER`](#isa-section-emitnullifier) |
Emit a new nullifier to be inserted into the nullifier tree |
{`context.worldState.nullifiers.append(
@@ -334,7 +341,7 @@ M[existsOffset] = exists`}
|
- 0x2c | [`L1TOL2MSGEXISTS`](#isa-section-l1tol2msgexists) |
+ 0x2d | [`L1TOL2MSGEXISTS`](#isa-section-l1tol2msgexists) |
Check if a message exists in the L1-to-L2 message tree |
{`exists = context.worldState.l1ToL2Messages.has({
@@ -344,7 +351,7 @@ M[existsOffset] = exists`}
|
- 0x2d | [`HEADERMEMBER`](#isa-section-headermember) |
+ 0x2e | [`HEADERMEMBER`](#isa-section-headermember) |
Check if a header exists in the [archive tree](../state/archive) and retrieve the specified member if so |
{`exists = context.worldState.header.has({
@@ -357,7 +364,7 @@ if exists:
|
- 0x2e | [`GETCONTRACTINSTANCE`](#isa-section-getcontractinstance) |
+ 0x2f | [`GETCONTRACTINSTANCE`](#isa-section-getcontractinstance) |
Copies contract instance data to memory |
{`M[dstOffset:dstOffset+CONTRACT_INSTANCE_SIZE+1] = [
@@ -372,7 +379,7 @@ if exists:
|
- 0x2f | [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog) |
+ 0x30 | [`EMITUNENCRYPTEDLOG`](#isa-section-emitunencryptedlog) |
Emit an unencrypted log |
{`context.accruedSubstate.unencryptedLogs.append(
@@ -385,7 +392,7 @@ if exists:
|
- 0x30 | [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) |
+ 0x31 | [`SENDL2TOL1MSG`](#isa-section-sendl2tol1msg) |
Send an L2-to-L1 message |
{`context.accruedSubstate.sentL2ToL1Messages.append(
@@ -398,7 +405,7 @@ if exists:
|
- 0x31 | [`CALL`](#isa-section-call) |
+ 0x32 | [`CALL`](#isa-section-call) |
Call into another contract |
{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize }
@@ -412,7 +419,7 @@ updateContextAfterNestedCall(context, instr.args, nestedContext)`}
|
- 0x32 | [`STATICCALL`](#isa-section-staticcall) |
+ 0x33 | [`STATICCALL`](#isa-section-staticcall) |
Call into another contract, disallowing World State and Accrued Substate modifications |
{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize }
@@ -426,7 +433,7 @@ updateContextAfterNestedCall(context, instr.args, nestedContext)`}
|
- 0x33 | [`DELEGATECALL`](#isa-section-delegatecall) |
+ 0x34 | [`DELEGATECALL`](#isa-section-delegatecall) |
Call into another contract, but keep the caller's `sender` and `storageAddress` |
{`// instr.args are { gasOffset, addrOffset, argsOffset, retOffset, retSize }
@@ -440,7 +447,7 @@ updateContextAfterNestedCall(context, instr.args, nestedContext)`}
|
- 0x34 | [`RETURN`](#isa-section-return) |
+ 0x35 | [`RETURN`](#isa-section-return) |
Halt execution within this context (without revert), optionally returning some data |
{`context.contractCallResults.output = M[retOffset:retOffset+retSize]
@@ -448,7 +455,7 @@ halt`}
|
- 0x35 | [`REVERT`](#isa-section-revert) |
+ 0x36 | [`REVERT`](#isa-section-revert) |
Halt execution within this context as `reverted`, optionally returning some data |
{`context.contractCallResults.output = M[retOffset:retOffset+retSize]
@@ -456,6 +463,11 @@ context.contractCallResults.reverted = true
halt`}
|
+
+ 0x37 | [`TORADIXLE`](#isa-section-to_radix_le) |
+ Convert a word to an array of limbs in little-endian radix form |
+ TBD: Storage of limbs and if T[dstOffset] is constrained to U8 |
+
@@ -862,12 +874,28 @@ Get the fee to be paid per "DA gas" - constant for entire transaction
[![](/img/protocol-specs/public-vm/bit-formats/FEEPERDAGAS.png)](/img/protocol-specs/public-vm/bit-formats/FEEPERDAGAS.png)
+### `TRANSACTIONFEE`
+Get the computed transaction fee during teardown phase, zero otherwise
+
+[See in table.](#isa-table-transactionfee)
+
+- **Opcode**: 0x14
+- **Category**: Execution Environment
+- **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**:
+ - **dstOffset**: memory offset specifying where to store operation's result
+- **Expression**: `M[dstOffset] = context.environment.transactionFee`
+- **Tag updates**: `T[dstOffset] = u32`
+- **Bit-size**: 56
+
+
### `CONTRACTCALLDEPTH`
Get how many contract calls deep the current call context is
[See in table.](#isa-table-contractcalldepth)
-- **Opcode**: 0x14
+- **Opcode**: 0x15
- **Category**: Execution Environment
- **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]`.
@@ -885,7 +913,7 @@ Get this rollup's L1 chain ID
[See in table.](#isa-table-chainid)
-- **Opcode**: 0x15
+- **Opcode**: 0x16
- **Category**: Execution Environment - Globals
- **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]`.
@@ -902,7 +930,7 @@ Get this rollup's L2 version ID
[See in table.](#isa-table-version)
-- **Opcode**: 0x16
+- **Opcode**: 0x17
- **Category**: Execution Environment - Globals
- **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]`.
@@ -919,7 +947,7 @@ Get this L2 block's number
[See in table.](#isa-table-blocknumber)
-- **Opcode**: 0x17
+- **Opcode**: 0x18
- **Category**: Execution Environment - Globals
- **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]`.
@@ -936,7 +964,7 @@ Get this L2 block's timestamp
[See in table.](#isa-table-timestamp)
-- **Opcode**: 0x18
+- **Opcode**: 0x19
- **Category**: Execution Environment - Globals
- **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]`.
@@ -953,7 +981,7 @@ Get the block's beneficiary address
[See in table.](#isa-table-coinbase)
-- **Opcode**: 0x19
+- **Opcode**: 0x1a
- **Category**: Execution Environment - Globals
- **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]`.
@@ -970,7 +998,7 @@ Total amount of "L2 gas" that a block can consume
[See in table.](#isa-table-blockl2gaslimit)
-- **Opcode**: 0x1a
+- **Opcode**: 0x1b
- **Category**: Execution Environment - Globals
- **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]`.
@@ -987,7 +1015,7 @@ Total amount of "DA gas" that a block can consume
[See in table.](#isa-table-blockdagaslimit)
-- **Opcode**: 0x1b
+- **Opcode**: 0x1c
- **Category**: Execution Environment - Globals
- **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]`.
@@ -1004,7 +1032,7 @@ Copy calldata into memory
[See in table.](#isa-table-calldatacopy)
-- **Opcode**: 0x1c
+- **Opcode**: 0x1d
- **Category**: Execution Environment - Calldata
- **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]`.
@@ -1024,7 +1052,7 @@ Remaining "L2 gas" for this call (after this instruction)
[See in table.](#isa-table-l2gasleft)
-- **Opcode**: 0x1d
+- **Opcode**: 0x1e
- **Category**: Machine State - Gas
- **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]`.
@@ -1041,7 +1069,7 @@ Remaining "DA gas" for this call (after this instruction)
[See in table.](#isa-table-dagasleft)
-- **Opcode**: 0x1e
+- **Opcode**: 0x1f
- **Category**: Machine State - Gas
- **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]`.
@@ -1058,7 +1086,7 @@ Jump to a location in the bytecode
[See in table.](#isa-table-jump)
-- **Opcode**: 0x1f
+- **Opcode**: 0x20
- **Category**: Machine State - Control Flow
- **Args**:
- **loc**: target location to jump to
@@ -1073,7 +1101,7 @@ Conditionally jump to a location in the bytecode
[See in table.](#isa-table-jumpi)
-- **Opcode**: 0x20
+- **Opcode**: 0x21
- **Category**: Machine State - Control Flow
- **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]`.
@@ -1091,7 +1119,7 @@ Make an internal call. Push the current PC to the internal call stack and jump t
[See in table.](#isa-table-internalcall)
-- **Opcode**: 0x21
+- **Opcode**: 0x22
- **Category**: Machine State - Control Flow
- **Args**:
- **loc**: target location to jump/call to
@@ -1103,14 +1131,13 @@ context.machineState.pc = loc`}
- **Details**: Target location is an immediate value (a constant in the bytecode).
- **Bit-size**: 48
-[![](/img/protocol-specs/public-vm/bit-formats/INTERNALCALL.png)](/img/protocol-specs/public-vm/bit-formats/INTERNALCALL.png)
### `INTERNALRETURN`
Return from an internal call. Pop from the internal call stack and jump to the popped location.
[See in table.](#isa-table-internalreturn)
-- **Opcode**: 0x22
+- **Opcode**: 0x23
- **Category**: Machine State - Control Flow
- **Expression**: `context.machineState.pc = context.machineState.internalCallStack.pop()`
- **Bit-size**: 16
@@ -1122,7 +1149,7 @@ Set a memory word from a constant in the bytecode
[See in table.](#isa-table-set)
-- **Opcode**: 0x23
+- **Opcode**: 0x24
- **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]`.
@@ -1142,7 +1169,7 @@ Move a word from source memory location to destination
[See in table.](#isa-table-mov)
-- **Opcode**: 0x24
+- **Opcode**: 0x25
- **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]`.
@@ -1160,7 +1187,7 @@ Move a word (conditionally chosen) from one memory location to another (`d = con
[See in table.](#isa-table-cmov)
-- **Opcode**: 0x25
+- **Opcode**: 0x26
- **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]`.
@@ -1181,7 +1208,7 @@ Load a word from this contract's persistent public storage. Zero is loaded for u
[See in table.](#isa-table-sload)
-- **Opcode**: 0x26
+- **Opcode**: 0x27
- **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]`.
@@ -1226,7 +1253,7 @@ Write a word to this contract's persistent public storage
[See in table.](#isa-table-sstore)
-- **Opcode**: 0x27
+- **Opcode**: 0x28
- **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]`.
@@ -1266,7 +1293,7 @@ Check whether a note hash exists in the note hash tree (as of the start of the c
[See in table.](#isa-table-notehashexists)
-- **Opcode**: 0x28
+- **Opcode**: 0x29
- **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]`.
@@ -1304,7 +1331,7 @@ Emit a new note hash to be inserted into the note hash tree
[See in table.](#isa-table-emitnotehash)
-- **Opcode**: 0x29
+- **Opcode**: 0x2a
- **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]`.
@@ -1336,7 +1363,7 @@ Check whether a nullifier exists in the nullifier tree (including nullifiers fro
[See in table.](#isa-table-nullifierexists)
-- **Opcode**: 0x2a
+- **Opcode**: 0x2b
- **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 +1400,7 @@ Emit a new nullifier to be inserted into the nullifier tree
[See in table.](#isa-table-emitnullifier)
-- **Opcode**: 0x2b
+- **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]`.
@@ -1405,7 +1432,7 @@ Check if a message exists in the L1-to-L2 message tree
[See in table.](#isa-table-l1tol2msgexists)
-- **Opcode**: 0x2c
+- **Opcode**: 0x2d
- **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]`.
@@ -1444,7 +1471,7 @@ Check if a header exists in the [archive tree](../state/archive) and retrieve th
[See in table.](#isa-table-headermember)
-- **Opcode**: 0x2d
+- **Opcode**: 0x2e
- **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]`.
@@ -1487,7 +1514,7 @@ Copies contract instance data to memory
[See in table.](#isa-table-getcontractinstance)
-- **Opcode**: 0x2e
+- **Opcode**: 0x2f
- **Category**: Other
- **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]`.
@@ -1517,7 +1544,7 @@ Emit an unencrypted log
[See in table.](#isa-table-emitunencryptedlog)
-- **Opcode**: 0x2f
+- **Opcode**: 0x30
- **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]`.
@@ -1544,7 +1571,7 @@ Send an L2-to-L1 message
[See in table.](#isa-table-sendl2tol1msg)
-- **Opcode**: 0x30
+- **Opcode**: 0x31
- **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]`.
@@ -1570,7 +1597,7 @@ Call into another contract
[See in table.](#isa-table-call)
-- **Opcode**: 0x31
+- **Opcode**: 0x32
- **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]`.
@@ -1617,7 +1644,7 @@ Call into another contract, disallowing World State and Accrued Substate modific
[See in table.](#isa-table-staticcall)
-- **Opcode**: 0x32
+- **Opcode**: 0x33
- **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]`.
@@ -1661,7 +1688,7 @@ Call into another contract, but keep the caller's `sender` and `storageAddress`
[See in table.](#isa-table-delegatecall)
-- **Opcode**: 0x33
+- **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]`.
@@ -1705,7 +1732,7 @@ Halt execution within this context (without revert), optionally returning some d
[See in table.](#isa-table-return)
-- **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]`.
@@ -1727,7 +1754,7 @@ Halt execution within this context as `reverted`, optionally returning some data
[See in table.](#isa-table-revert)
-- **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]`.
@@ -1744,3 +1771,23 @@ halt`}
- **Bit-size**: 88
[![](/img/protocol-specs/public-vm/bit-formats/REVERT.png)](/img/protocol-specs/public-vm/bit-formats/REVERT.png)
+
+### `TORADIXLE`
+Convert a word to an array of limbs in little-endian radix form
+
+[See in table.](#isa-table-to_radix_le)
+
+- **Opcode**: 0x37
+- **Category**: 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]`.
+- **Args**:
+ - **srcOffset**: memory offset of word to convert.
+ - **dstOffset**: memory offset specifying where the first limb of the radix-conversion result is stored.
+ - **radix**: the maximum bit-size of each limb.
+ - **numLimbs**: the number of limbs the word will be converted into.
+- **Expression**: TBD: Storage of limbs and if T[dstOffset] is constrained to U8
+- **Details**: The limbs will be stored in a contiguous memory block starting at `dstOffset`.
+- **Tag checks**: `T[srcOffset] == field`
+- **Bit-size**: 152
+
diff --git a/docs/src/preprocess/InstructionSet/InstructionSet.js b/docs/src/preprocess/InstructionSet/InstructionSet.js
index a94e3b933ab..fac2b3e0239 100644
--- a/docs/src/preprocess/InstructionSet/InstructionSet.js
+++ b/docs/src/preprocess/InstructionSet/InstructionSet.js
@@ -1537,6 +1537,39 @@ halt
"Tag checks": "",
"Tag updates": "",
},
+ {
+ id: "to_radix_le",
+ Name: "`TORADIXLE`",
+ Category: "Conversions",
+ Flags: [{ name: "indirect", description: INDIRECT_FLAG_DESCRIPTION }],
+ Args: [
+ {
+ name: "srcOffset",
+ description: "memory offset of word to convert.",
+ },
+ {
+ name: "dstOffset",
+ description: "memory offset specifying where the first limb of the radix-conversion result is stored.",
+ },
+ {
+ name: "radix",
+ description: "the maximum bit-size of each limb.",
+ mode: "immediate",
+ type: "u32",
+ },
+ {
+ name: "numLimbs",
+ description: "the number of limbs the word will be converted into.",
+ type: "u32",
+ mode: "immediate",
+ }
+ ],
+
+ Expression: `TBD: Storage of limbs and if T[dstOffset] is constrained to U8`,
+ Summary: "Convert a word to an array of limbs in little-endian radix form",
+ Details: "The limbs will be stored in a contiguous memory block starting at `dstOffset`.",
+ "Tag checks": "`T[srcOffset] == field`",
+ }
];
const INSTRUCTION_SET = INSTRUCTION_SET_RAW.map((instr) => {
instr["Bit-size"] = instructionSize(instr);
diff --git a/yarn-project/simulator/src/avm/avm_gas.ts b/yarn-project/simulator/src/avm/avm_gas.ts
index 8f140ed03e1..b16b171212f 100644
--- a/yarn-project/simulator/src/avm/avm_gas.ts
+++ b/yarn-project/simulator/src/avm/avm_gas.ts
@@ -123,6 +123,8 @@ export const GasCosts: Record = {
[Opcode.POSEIDON2]: TemporaryDefaultGasCost,
[Opcode.SHA256]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,
[Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t
+ // Conversions
+ [Opcode.TORADIXLE]: TemporaryDefaultGasCost,
};
/** Returns the fixed base gas cost for a given opcode, or throws if set to dynamic. */
diff --git a/yarn-project/simulator/src/avm/opcodes/conversion.test.ts b/yarn-project/simulator/src/avm/opcodes/conversion.test.ts
new file mode 100644
index 00000000000..d3278b0871f
--- /dev/null
+++ b/yarn-project/simulator/src/avm/opcodes/conversion.test.ts
@@ -0,0 +1,90 @@
+import { type AvmContext } from '../avm_context.js';
+import { Field, type Uint8, Uint32 } from '../avm_memory_types.js';
+import { initContext } from '../fixtures/index.js';
+import { Addressing, AddressingMode } from './addressing_mode.js';
+import { ToRadixLE } from './conversion.js';
+
+describe('Conversion Opcodes', () => {
+ let context: AvmContext;
+
+ beforeEach(async () => {
+ context = initContext();
+ });
+
+ describe('To Radix LE', () => {
+ it('Should (de)serialize correctly', () => {
+ const buf = Buffer.from([
+ ToRadixLE.opcode, // opcode
+ 1, // indirect
+ ...Buffer.from('12345678', 'hex'), // inputStateOffset
+ ...Buffer.from('23456789', 'hex'), // outputStateOffset
+ ...Buffer.from('00000002', 'hex'), // radix
+ ...Buffer.from('00000100', 'hex'), // numLimbs
+ ]);
+ const inst = new ToRadixLE(
+ /*indirect=*/ 1,
+ /*srcOffset=*/ 0x12345678,
+ /*dstOffset=*/ 0x23456789,
+ /*radix=*/ 2,
+ /*numLimbs=*/ 256,
+ );
+
+ expect(ToRadixLE.deserialize(buf)).toEqual(inst);
+ expect(inst.serialize()).toEqual(buf);
+ });
+
+ it('Should decompose correctly - direct', async () => {
+ const arg = new Field(0b1011101010100n);
+ const indirect = 0;
+ const srcOffset = 0;
+ const radix = 2; // Bit decomposition
+ const numLimbs = 10; // only the first 10 bits
+ const dstOffset = 20;
+ context.machineState.memory.set(srcOffset, arg);
+
+ await new ToRadixLE(indirect, srcOffset, dstOffset, radix, numLimbs).execute(context);
+
+ const resultBuffer: Buffer = Buffer.concat(
+ context.machineState.memory.getSliceAs(dstOffset, numLimbs).map(byte => byte.toBuffer()),
+ );
+ // The expected result is the first 10 bits of the input, reversed
+ const expectedResults = '1011101010100'.split('').reverse().slice(0, numLimbs).map(Number);
+ for (let i = 0; i < numLimbs; i++) {
+ expect(resultBuffer.readUInt8(i)).toEqual(expectedResults[i]);
+ }
+ });
+
+ it('Should decompose correctly - indirect', async () => {
+ const arg = new Field(Buffer.from('1234567890abcdef', 'hex'));
+ const indirect = new Addressing([
+ /*srcOffset=*/ AddressingMode.INDIRECT,
+ /*dstOffset*/ AddressingMode.INDIRECT,
+ ]).toWire();
+ const srcOffset = 0;
+ const srcOffsetReal = 10;
+ const dstOffset = 2;
+ const dstOffsetReal = 30;
+ context.machineState.memory.set(srcOffset, new Uint32(srcOffsetReal));
+ context.machineState.memory.set(dstOffset, new Uint32(dstOffsetReal));
+ context.machineState.memory.set(srcOffsetReal, arg);
+
+ const radix = 1 << 8; // Byte decomposition
+ const numLimbs = 32; // 256-bit decomposition
+ await new ToRadixLE(indirect, srcOffset, dstOffset, radix, numLimbs).execute(context);
+
+ const resultBuffer: Buffer = Buffer.concat(
+ context.machineState.memory.getSliceAs(dstOffsetReal, numLimbs).map(byte => byte.toBuffer()),
+ );
+ // The expected result is the input (padded to 256 bits),and reversed
+ const expectedResults = '1234567890abcdef'
+ .padStart(64, '0')
+ .split('')
+ .reverse()
+ .map(a => parseInt(a, 16));
+ // Checking the value in each byte of the buffer is correct
+ for (let i = 0; i < numLimbs; i++) {
+ expect(resultBuffer.readUInt8(i)).toEqual(expectedResults[2 * i] + expectedResults[2 * i + 1] * 16);
+ }
+ });
+ });
+});
diff --git a/yarn-project/simulator/src/avm/opcodes/conversion.ts b/yarn-project/simulator/src/avm/opcodes/conversion.ts
new file mode 100644
index 00000000000..dc9884d9aab
--- /dev/null
+++ b/yarn-project/simulator/src/avm/opcodes/conversion.ts
@@ -0,0 +1,58 @@
+import { assert } from '../../../../foundation/src/json-rpc/js_utils.js';
+import { type AvmContext } from '../avm_context.js';
+import { TypeTag, Uint8 } from '../avm_memory_types.js';
+import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
+import { Addressing } from './addressing_mode.js';
+import { Instruction } from './instruction.js';
+
+export class ToRadixLE extends Instruction {
+ static type: string = 'TORADIXLE';
+ static readonly opcode: Opcode = Opcode.TORADIXLE;
+
+ // Informs (de)serialization. See Instruction.deserialize.
+ static readonly wireFormat: OperandType[] = [
+ OperandType.UINT8, // Opcode
+ OperandType.UINT8, // Indirect
+ OperandType.UINT32, // src memory address
+ OperandType.UINT32, // dst memory address
+ OperandType.UINT32, // radix (immediate)
+ OperandType.UINT32, // number of limbs (Immediate)
+ ];
+
+ constructor(
+ private indirect: number,
+ private srcOffset: number,
+ private dstOffset: number,
+ private radix: number,
+ private numLimbs: number,
+ ) {
+ assert(radix <= 256, 'Radix cannot be greater than 256');
+ super();
+ }
+
+ public async execute(context: AvmContext): Promise {
+ const memory = context.machineState.memory.track(this.type);
+ const [srcOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.srcOffset, this.dstOffset], memory);
+ const memoryOperations = { reads: 1, writes: this.numLimbs, indirect: this.indirect };
+ context.machineState.consumeGas(this.gasCost(memoryOperations));
+
+ // The radix gadget only takes in a Field
+ memory.checkTag(TypeTag.FIELD, srcOffset);
+
+ let value: bigint = memory.get(srcOffset).toBigInt();
+ const radixBN: bigint = BigInt(this.radix);
+ const limbArray = [];
+
+ for (let i = 0; i < this.numLimbs; i++) {
+ const limb = value % radixBN;
+ limbArray.push(limb);
+ value /= radixBN;
+ }
+
+ const res = [...limbArray].map(byte => new Uint8(byte));
+ memory.setSlice(dstOffset, res);
+
+ memory.assert(memoryOperations);
+ context.machineState.incrementPc();
+ }
+}
diff --git a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts
index 569ad1d7eda..dabf361d04c 100644
--- a/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts
+++ b/yarn-project/simulator/src/avm/serialization/instruction_serialization.ts
@@ -74,6 +74,8 @@ export enum Opcode {
POSEIDON2,
SHA256, // temp - may be removed, but alot of contracts rely on it
PEDERSEN, // temp - may be removed, but alot of contracts rely on it
+ // Conversion
+ TORADIXLE,
}
// Possible types for an instruction's operand in its wire format. (Keep in sync with CPP code.