diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 7fe387c6eec1..56a95704d7d2 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -594,33 +594,31 @@ fn handle_external_call( inputs: &[ValueOrArray], opcode: AvmOpcode, ) { - if !destinations.is_empty() || inputs.len() != 4 { + if !destinations.is_empty() || inputs.len() != 5 { panic!( - "Transpiler expects ForeignCall (Static)Call to have 0 destinations and 4 inputs, got {} and {}.", + "Transpiler expects ForeignCall (Static)Call to have 0 destinations and 5 inputs, got {} and {}.", destinations.len(), inputs.len() ); } - let gas_offset_ptr = match &inputs[0] { - ValueOrArray::HeapArray(HeapArray { pointer, size }) => { - assert!( - *size == 2, - "Call instruction's gas input should be a HeapArray of size 2 (`[l2Gas, daGas]`)" - ); - pointer - } - _ => panic!("Call instruction's gas input should be a HeapArray"), + let l2_gas_offset = match &inputs[0] { + ValueOrArray::MemoryAddress(offset) => offset, + _ => panic!("Call instruction's gas input should be a basic MemoryAddress"), + }; + let da_gas_offset = match &inputs[1] { + ValueOrArray::MemoryAddress(offset) => offset, + _ => panic!("Call instruction's gas input should be a basic MemoryAddress"), }; - let address_offset = match &inputs[1] { + let address_offset = match &inputs[2] { ValueOrArray::MemoryAddress(offset) => offset, _ => panic!("Call instruction's target address input should be a basic MemoryAddress",), }; // The args are a slice, and this is represented as a (Field, HeapVector). // The field is the length (memory address) and the HeapVector has the data and length again. // This is an ACIR internal representation detail that leaks to the SSA. - // Observe that below, we use `inputs[3]` and therefore skip the length field. - let args = &inputs[3]; + // Observe that below, we use `inputs[4]` and therefore skip the length field. + let args = &inputs[4]; let (args_offset_ptr, args_size_offset) = match args { ValueOrArray::HeapVector(HeapVector { pointer, size }) => (pointer, size), _ => panic!("Call instruction's args input should be a HeapVector input"), @@ -630,14 +628,16 @@ fn handle_external_call( opcode, indirect: Some( AddressingModeBuilder::default() - .indirect_operand(gas_offset_ptr) + .direct_operand(l2_gas_offset) + .direct_operand(da_gas_offset) .direct_operand(address_offset) .indirect_operand(args_offset_ptr) .direct_operand(args_size_offset) .build(), ), operands: vec![ - AvmOperand::U16 { value: gas_offset_ptr.to_usize() as u16 }, + AvmOperand::U16 { value: l2_gas_offset.to_usize() as u16 }, + AvmOperand::U16 { value: da_gas_offset.to_usize() as u16 }, AvmOperand::U16 { value: address_offset.to_usize() as u16 }, AvmOperand::U16 { value: args_offset_ptr.to_usize() as u16 }, AvmOperand::U16 { value: args_size_offset.to_usize() as u16 }, diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp index 2db13c165e17..bd0115807752 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/serialization.cpp @@ -40,11 +40,11 @@ const std::vector three_operand_format16 = { const std::vector kernel_input_operand_format = { OperandType::INDIRECT8, OperandType::UINT16 }; const std::vector external_call_format = { OperandType::INDIRECT16, - /*gasOffset=*/OperandType::UINT16, + /*l2GasOffset=*/OperandType::UINT16, + /*daGasOffset=*/OperandType::UINT16, /*addrOffset=*/OperandType::UINT16, /*argsOffset=*/OperandType::UINT16, - /*argsSizeOffset=*/OperandType::UINT16, - /*successOffset=*/OperandType::UINT16 }; + /*argsSizeOffset=*/OperandType::UINT16 }; // Contrary to TS, the format does not contain the WireOpCode byte which prefixes any instruction. // The format for WireOpCode::SET has to be handled separately as it is variable based on the tag. diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 42ac8130fdd4..091f7495adca 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -86,7 +86,12 @@ impl PublicContext { ) -> [Field] { let calldata = args.push_front(function_selector.to_field()); - call(gas_for_call(gas_opts), contract_address, calldata); + call( + gas_opts.l2_gas.unwrap_or(MAX_FIELD_VALUE), + gas_opts.da_gas.unwrap_or(MAX_FIELD_VALUE), + contract_address, + calldata, + ); // Use success_copy to determine whether the call succeeded let success = success_copy(); @@ -107,7 +112,12 @@ impl PublicContext { ) -> [Field] { let calldata = args.push_front(function_selector.to_field()); - call_static(gas_for_call(gas_opts), contract_address, calldata); + call_static( + gas_opts.l2_gas.unwrap_or(MAX_FIELD_VALUE), + gas_opts.da_gas.unwrap_or(MAX_FIELD_VALUE), + contract_address, + calldata, + ); // Use success_copy to determine whether the call succeeded let success = success_copy(); @@ -245,13 +255,6 @@ impl PublicContext { } } -// Helper functions -fn gas_for_call(user_gas: GasOpts) -> [Field; 2] { - // It's ok to use the max possible gas here, because the gas will be - // capped by the gas left in the (STATIC)CALL instruction. - [user_gas.l2_gas.unwrap_or(MAX_FIELD_VALUE), user_gas.da_gas.unwrap_or(MAX_FIELD_VALUE)] -} - // Unconstrained opcode wrappers (do not use directly). unconstrained fn address() -> AztecAddress { address_opcode() @@ -310,12 +313,22 @@ unconstrained fn l1_to_l2_msg_exists(msg_hash: Field, msg_leaf_index: Field) -> unconstrained fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) { send_l2_to_l1_msg_opcode(recipient, content) } -unconstrained fn call(gas: [Field; 2], address: AztecAddress, args: [Field]) { - call_opcode(gas, address, args) +unconstrained fn call( + l2_gas_allocation: Field, + da_gas_allocation: Field, + address: AztecAddress, + args: [Field], +) { + call_opcode(l2_gas_allocation, da_gas_allocation, address, args) } -unconstrained fn call_static(gas: [Field; 2], address: AztecAddress, args: [Field]) { - call_static_opcode(gas, address, args) +unconstrained fn call_static( + l2_gas_allocation: Field, + da_gas_allocation: Field, + address: AztecAddress, + args: [Field], +) { + call_static_opcode(l2_gas_allocation, da_gas_allocation, address, args) } pub unconstrained fn calldata_copy(cdoffset: u32, copy_size: u32) -> [Field; N] { @@ -441,14 +454,16 @@ unconstrained fn revert_opcode(revertdata: [Field]) {} #[oracle(avmOpcodeCall)] unconstrained fn call_opcode( - gas: [Field; 2], // gas allocation: [l2_gas, da_gas] + l2_gas_allocation: Field, + da_gas_allocation: Field, address: AztecAddress, args: [Field], ) {} #[oracle(avmOpcodeStaticCall)] unconstrained fn call_static_opcode( - gas: [Field; 2], // gas allocation: [l2_gas, da_gas] + l2_gas_allocation: Field, + da_gas_allocation: Field, address: AztecAddress, args: [Field], ) {} diff --git a/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr index e71ec598826c..827a0cf726eb 100644 --- a/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr @@ -12,7 +12,7 @@ pub contract AvmTest { // Libs use dep::aztec::context::gas::GasOpts; - use dep::aztec::context::public_context::{call, gas_for_call, returndata_size, success_copy}; + use dep::aztec::context::public_context::{call, returndata_size, success_copy}; use dep::aztec::macros::{functions::{private, public}, storage::storage}; use dep::aztec::oracle::get_contract_instance::{ get_contract_instance_class_id_avm, get_contract_instance_deployer_avm, @@ -257,12 +257,12 @@ pub contract AvmTest { // since it will all be consumed on exceptional halt. let l2_gas_left = context.l2_gas_left(); let da_gas_left = context.da_gas_left(); - let gas_allocation: [Field; 2] = [l2_gas_left - 200_000, da_gas_left - 200_000]; let selector = FunctionSelector::from_signature("divide_by_zero()"); // Call without capturing a return value since call no longer returns success call( - gas_allocation, + l2_gas_left - 200_000, + da_gas_left - 200_000, context.this_address(), &[selector.to_field()], ); @@ -540,8 +540,7 @@ pub contract AvmTest { #[public] fn nested_call_to_nothing_recovers() { let garbageAddress = AztecAddress::from_field(42); - let gas = [1, 1]; - call(gas, garbageAddress, &[]); + call(1, 1, garbageAddress, &[]); let success = success_copy(); assert( !success, diff --git a/yarn-project/simulator/src/public/avm/opcodes/external_calls.test.ts b/yarn-project/simulator/src/public/avm/opcodes/external_calls.test.ts index ef67d027ba97..511d2299dc66 100644 --- a/yarn-project/simulator/src/public/avm/opcodes/external_calls.test.ts +++ b/yarn-project/simulator/src/public/avm/opcodes/external_calls.test.ts @@ -45,15 +45,17 @@ describe('External Calls', () => { it('Should (de)serialize correctly', () => { const buf = Buffer.from([ Call.opcode, // opcode - ...Buffer.from('12', 'hex'), // indirect (8 bit) - ...Buffer.from('1234', 'hex'), // gasOffset + ...Buffer.from('1234', 'hex'), // indirect (16 bit) + ...Buffer.from('1234', 'hex'), // l2GasOffset + ...Buffer.from('5678', 'hex'), // daGasOffset ...Buffer.from('a234', 'hex'), // addrOffset ...Buffer.from('b234', 'hex'), // argsOffset ...Buffer.from('c234', 'hex'), // argsSizeOffset ]); const inst = new Call( - /*indirect=*/ 0x12, - /*gasOffset=*/ 0x1234, + /*indirect=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSizeOffset=*/ 0xc234, @@ -64,7 +66,8 @@ describe('External Calls', () => { }); it('Call to non-existent bytecode returns failure', async () => { - const gasOffset = 0; + const l2GasOffset = 0; + const daGasOffset = 1; const l2Gas = 2e6; const daGas = 3e6; const addrOffset = 2; @@ -84,7 +87,7 @@ describe('External Calls', () => { context.machineState.memory.set(argsSizeOffset, new Uint32(argsSize)); context.machineState.memory.setSlice(3, args); - const instruction = new Call(/*indirect=*/ 0, gasOffset, addrOffset, argsOffset, argsSizeOffset); + const instruction = new Call(/*indirect=*/ 0, l2GasOffset, daGasOffset, addrOffset, argsOffset, argsSizeOffset); await instruction.execute(context); // Use SuccessCopy to get success @@ -104,7 +107,8 @@ describe('External Calls', () => { }); it('Should execute a call correctly', async () => { - const gasOffset = 0; + const l2GasOffset = 0; + const daGasOffset = 1; const l2Gas = 2e6; const daGas = 3e6; const addrOffset = 2; @@ -138,7 +142,7 @@ describe('External Calls', () => { context.machineState.memory.set(argsSizeOffset, new Uint32(argsSize)); context.machineState.memory.setSlice(3, args); - const instruction = new Call(/*indirect=*/ 0, gasOffset, addrOffset, argsOffset, argsSizeOffset); + const instruction = new Call(/*indirect=*/ 0, l2GasOffset, daGasOffset, addrOffset, argsOffset, argsSizeOffset); await instruction.execute(context); // Use SuccessCopy to get success @@ -156,7 +160,8 @@ describe('External Calls', () => { }); it('Should cap to available gas if allocated is bigger', async () => { - const gasOffset = 0; + const l2GasOffset = 0; + const daGasOffset = 1; const l2Gas = 1e9; const daGas = 1e9; const addrOffset = 2; @@ -190,7 +195,14 @@ describe('External Calls', () => { context.machineState.memory.set(2, new Field(addr)); context.machineState.memory.set(argsSizeOffset, new Uint32(argsSize)); - const instruction = new Call(/*indirect=*/ 0, gasOffset, addrOffset, /*argsOffset=*/ 0, argsSizeOffset); + const instruction = new Call( + /*indirect=*/ 0, + l2GasOffset, + daGasOffset, + addrOffset, + /*argsOffset=*/ 0, + argsSizeOffset, + ); await instruction.execute(context); // Use SuccessCopy to get success @@ -213,15 +225,17 @@ describe('External Calls', () => { it('Should (de)serialize correctly', () => { const buf = Buffer.from([ StaticCall.opcode, // opcode - ...Buffer.from('12', 'hex'), // indirect (8 bit) - ...Buffer.from('1234', 'hex'), // gasOffset + ...Buffer.from('1234', 'hex'), // indirect (16 bit) + ...Buffer.from('1234', 'hex'), // l2GasOffset + ...Buffer.from('5678', 'hex'), // daGasOffset ...Buffer.from('a234', 'hex'), // addrOffset ...Buffer.from('b234', 'hex'), // argsOffset ...Buffer.from('c234', 'hex'), // argsSizeOffset ]); const inst = new StaticCall( - /*indirect=*/ 0x12, - /*gasOffset=*/ 0x1234, + /*indirect=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSizeOffset=*/ 0xc234, @@ -232,7 +246,8 @@ describe('External Calls', () => { }); it('Should fail if a static call attempts to touch storage', async () => { - const gasOffset = 0; + const l2GasOffset = 0; + const daGasOffset = 1; const gas = [new Field(0n), new Field(0n), new Field(0n)]; const addrOffset = 10; const addr = new Field(123456n); @@ -242,7 +257,8 @@ describe('External Calls', () => { const argsSize = args.length; const argsSizeOffset = 60; - context.machineState.memory.setSlice(gasOffset, gas); + context.machineState.memory.set(l2GasOffset, gas[0]); + context.machineState.memory.set(daGasOffset, gas[1]); context.machineState.memory.set(addrOffset, addr); context.machineState.memory.set(argsSizeOffset, new Uint32(argsSize)); context.machineState.memory.setSlice(argsOffset, args); @@ -260,7 +276,14 @@ describe('External Calls', () => { const contractInstance = await makeContractInstanceFromClassId(contractClass.id); mockGetContractInstance(contractsDB, contractInstance); - const instruction = new StaticCall(/*indirect=*/ 0, gasOffset, addrOffset, argsOffset, argsSizeOffset); + const instruction = new StaticCall( + /*indirect=*/ 0, + l2GasOffset, + daGasOffset, + addrOffset, + argsOffset, + argsSizeOffset, + ); await instruction.execute(context); // Ideally we'd mock the nested call. expect(context.machineState.collectedRevertInfo?.recursiveRevertReason.message).toMatch( diff --git a/yarn-project/simulator/src/public/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/public/avm/opcodes/external_calls.ts index 4b66ef711870..a888816881be 100644 --- a/yarn-project/simulator/src/public/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/public/avm/opcodes/external_calls.ts @@ -9,16 +9,18 @@ abstract class ExternalCall extends Instruction { // Informs (de)serialization. See Instruction.deserialize. static readonly wireFormat: OperandType[] = [ OperandType.UINT8, - OperandType.UINT8, // Indirect - OperandType.UINT16, - OperandType.UINT16, - OperandType.UINT16, - OperandType.UINT16, + OperandType.UINT16, // Indirect + OperandType.UINT16, // L2 gas offset + OperandType.UINT16, // DA gas offset + OperandType.UINT16, // Address offset + OperandType.UINT16, // Args offset + OperandType.UINT16, // Args size offset ]; constructor( private indirect: number, - private gasOffset: number, + private l2GasOffset: number, + private daGasOffset: number, private addrOffset: number, private argsOffset: number, private argsSizeOffset: number, @@ -28,10 +30,12 @@ abstract class ExternalCall extends Instruction { public async execute(context: AvmContext) { const memory = context.machineState.memory; - const operands = [this.gasOffset, this.addrOffset, this.argsOffset, this.argsSizeOffset]; + const operands = [this.l2GasOffset, this.daGasOffset, this.addrOffset, this.argsOffset, this.argsSizeOffset]; const addressing = Addressing.fromWire(this.indirect, operands.length); - const [gasOffset, addrOffset, argsOffset, argsSizeOffset] = addressing.resolve(operands, memory); - memory.checkTags(TypeTag.FIELD, gasOffset, gasOffset + 1); + const [l2GasOffset, daGasOffset, addrOffset, argsOffset, argsSizeOffset] = addressing.resolve(operands, memory); + // TODO: Should be U32 + memory.checkTags(TypeTag.FIELD, l2GasOffset); + memory.checkTags(TypeTag.FIELD, daGasOffset); memory.checkTag(TypeTag.FIELD, addrOffset); memory.checkTag(TypeTag.UINT32, argsSizeOffset); @@ -49,9 +53,11 @@ abstract class ExternalCall extends Instruction { // Gas allocation is capped by the amount of gas left in the current context. // We have to do some dancing here because the gas allocation is a field, // but in the machine state we track gas as a number. - const allocatedL2Gas = Number(BigIntMin(memory.get(gasOffset).toBigInt(), BigInt(context.machineState.l2GasLeft))); + const allocatedL2Gas = Number( + BigIntMin(memory.get(l2GasOffset).toBigInt(), BigInt(context.machineState.l2GasLeft)), + ); const allocatedDaGas = Number( - BigIntMin(memory.get(gasOffset + 1).toBigInt(), BigInt(context.machineState.daGasLeft)), + BigIntMin(memory.get(daGasOffset).toBigInt(), BigInt(context.machineState.daGasLeft)), ); const allocatedGas = { l2Gas: allocatedL2Gas, daGas: allocatedDaGas }; context.machineState.consumeGas(allocatedGas); diff --git a/yarn-project/simulator/src/public/avm/serialization/bytecode_serialization.test.ts b/yarn-project/simulator/src/public/avm/serialization/bytecode_serialization.test.ts index 0c39f248b8bb..af385568e7bd 100644 --- a/yarn-project/simulator/src/public/avm/serialization/bytecode_serialization.test.ts +++ b/yarn-project/simulator/src/public/avm/serialization/bytecode_serialization.test.ts @@ -81,14 +81,16 @@ describe('Bytecode Serialization', () => { ), new Call( /*indirect=*/ 0x01, - /*gasOffset=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSize=*/ 0xc234, ), new StaticCall( /*indirect=*/ 0x01, - /*gasOffset=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSize=*/ 0xc234, @@ -111,14 +113,16 @@ describe('Bytecode Serialization', () => { ), new Call( /*indirect=*/ 0x01, - /*gasOffset=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSize=*/ 0xc234, ), new StaticCall( /*indirect=*/ 0x01, - /*gasOffset=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSize=*/ 0xc234, @@ -164,7 +168,8 @@ describe('Bytecode Serialization', () => { const instructions = [ new Call( /*indirect=*/ 0x01, - /*gasOffset=*/ 0x1234, + /*l2GasOffset=*/ 0x1234, + /*daGasOffset=*/ 0x5678, /*addrOffset=*/ 0xa234, /*argsOffset=*/ 0xb234, /*argsSize=*/ 0xc234, diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index e3b439687dbd..0981df3b47fd 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -763,7 +763,8 @@ export class TXEService { } async avmOpcodeCall( - _gas: ForeignCallArray, + _l2Gas: ForeignCallSingle, + _daGas: ForeignCallSingle, address: ForeignCallSingle, _length: ForeignCallSingle, args: ForeignCallArray, @@ -792,7 +793,8 @@ export class TXEService { } async avmOpcodeStaticCall( - _gas: ForeignCallArray, + _l2Gas: ForeignCallSingle, + _daGas: ForeignCallSingle, address: ForeignCallSingle, _length: ForeignCallSingle, args: ForeignCallArray,