diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 2a25f8242f9..093d90a8711 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -180,7 +180,7 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { BrilligOpcode::Stop { return_data_offset, return_data_size } => { avm_instrs.push(AvmInstruction { opcode: AvmOpcode::RETURN, - indirect: Some(ALL_DIRECT), + indirect: Some(ZEROTH_OPERAND_INDIRECT), operands: vec![ AvmOperand::U32 { value: *return_data_offset as u32 }, AvmOperand::U32 { value: *return_data_size as u32 }, @@ -239,9 +239,10 @@ fn handle_foreign_call( inputs: &Vec, ) { match function { + "avmOpcodeCall" => handle_external_call(avm_instrs, destinations, inputs), "amvOpcodeEmitUnencryptedLog" => { handle_emit_unencrypted_log(avm_instrs, destinations, inputs) - }, + } "avmOpcodeNoteHashExists" => handle_note_hash_exists(avm_instrs, destinations, inputs), "avmOpcodeEmitNoteHash" | "avmOpcodeEmitNullifier" => handle_emit_note_hash_or_nullifier( function == "avmOpcodeEmitNullifier", @@ -257,7 +258,7 @@ fn handle_foreign_call( } "avmOpcodePoseidon" => { handle_single_field_hash_instruction(avm_instrs, function, destinations, inputs) - }, + } "storageRead" => handle_storage_read(avm_instrs, destinations, inputs), "storageWrite" => handle_storage_write(avm_instrs, destinations, inputs), // Getters. @@ -272,6 +273,81 @@ fn handle_foreign_call( } } +/// Handle an AVM CALL +/// (an external 'call' brillig foreign call was encountered) +/// Adds the new instruction to the avm instructions list. +fn handle_external_call( + avm_instrs: &mut Vec, + destinations: &Vec, + inputs: &Vec, +) { + if destinations.len() != 2 || inputs.len() != 4 { + panic!( + "Transpiler expects ForeignCall::CALL to have 2 destinations and 4 inputs, got {} and {}. + Make sure your call instructions's input/return arrays have static length (`[Field; ]`)!", + destinations.len(), + inputs.len() + ); + } + let gas_offset_maybe = inputs[0]; + let gas_offset = match gas_offset_maybe { + ValueOrArray::HeapArray(HeapArray { pointer, size }) => { + assert!(size == 3, "Call instruction's gas input should be a HeapArray of size 3 (`[l1Gas, l2Gas, daGas]`)"); + pointer.0 as u32 + } + ValueOrArray::HeapVector(_) => panic!("Call instruction's gas input must be a HeapArray, not a HeapVector. Make sure you are explicitly defining its size as 3 (`[l1Gas, l2Gas, daGas]`)!"), + _ => panic!("Call instruction's gas input should be a HeapArray"), + }; + let address_offset = match &inputs[1] { + ValueOrArray::MemoryAddress(offset) => offset.to_usize() as u32, + _ => panic!("Call instruction's target address input should be a basic MemoryAddress",), + }; + let args_offset_maybe = inputs[2]; + let (args_offset, args_size) = match args_offset_maybe { + ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0 as u32, size as u32), + ValueOrArray::HeapVector(_) => panic!("Call instruction's args must be a HeapArray, not a HeapVector. Make sure you are explicitly defining its size (`[arg0, arg1, ... argN]`)!"), + _ => panic!("Call instruction's args input should be a HeapArray input"), + }; + let temporary_function_selector_offset = match &inputs[3] { + ValueOrArray::MemoryAddress(offset) => offset.to_usize() as u32, + _ => panic!( + "Call instruction's temporary function selector input should be a basic MemoryAddress", + ), + }; + + let ret_offset_maybe = destinations[0]; + let (ret_offset, ret_size) = match ret_offset_maybe { + ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0 as u32, size as u32), + ValueOrArray::HeapVector(_) => panic!("Call instruction's return data must be a HeapArray, not a HeapVector. Make sure you are explicitly defining its size (`let returnData: [Field; ] = ...`)!"), + _ => panic!("Call instruction's returnData destination should be a HeapArray input"), + }; + let success_offset = match &destinations[1] { + ValueOrArray::MemoryAddress(offset) => offset.to_usize() as u32, + _ => panic!("Call instruction's success destination should be a basic MemoryAddress",), + }; + avm_instrs.push(AvmInstruction { + opcode: AvmOpcode::CALL, + indirect: Some(0b01101), // (left to right) selector direct, ret offset INDIRECT, args offset INDIRECT, address offset direct, gas offset INDIRECT + operands: vec![ + AvmOperand::U32 { value: gas_offset }, + AvmOperand::U32 { + value: address_offset, + }, + AvmOperand::U32 { value: args_offset }, + AvmOperand::U32 { value: args_size }, + AvmOperand::U32 { value: ret_offset }, + AvmOperand::U32 { value: ret_size }, + AvmOperand::U32 { + value: success_offset, + }, + AvmOperand::U32 { + value: temporary_function_selector_offset, + }, + ], + ..Default::default() + }); +} + /// Handle an AVM NOTEHASHEXISTS instruction /// Adds the new instruction to the avm instructions list. fn handle_note_hash_exists( @@ -328,7 +404,10 @@ fn handle_emit_unencrypted_log( [ValueOrArray::MemoryAddress(offset), ValueOrArray::HeapArray(array)] => { (offset.to_usize() as u32, array) } - _ => panic!("Unexpected inputs for ForeignCall::EMITUNENCRYPTEDLOG: {:?}", inputs), + _ => panic!( + "Unexpected inputs for ForeignCall::EMITUNENCRYPTEDLOG: {:?}", + inputs + ), }; avm_instrs.push(AvmInstruction { opcode: AvmOpcode::EMITUNENCRYPTEDLOG, diff --git a/noir-projects/aztec-nr/aztec/src/context/avm.nr b/noir-projects/aztec-nr/aztec/src/context/avm.nr index 0d2c98e82e2..63a8a63d7ca 100644 --- a/noir-projects/aztec-nr/aztec/src/context/avm.nr +++ b/noir-projects/aztec-nr/aztec/src/context/avm.nr @@ -1,5 +1,6 @@ use dep::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH}; use dep::protocol_types::traits::{Serialize}; +use dep::protocol_types::abis::function_selector::FunctionSelector; // Getters that will be converted by the transpiler into their // own opcodes @@ -80,9 +81,20 @@ impl AVMContext { #[oracle(avmOpcodeSendL2ToL1Msg)] pub fn send_l2_to_l1_msg(self, recipient: EthAddress, content: Field) {} - /////////////////////////////////////////////////////////////////////////// - // The functions below allow interface-equivalence with PrivateContext - /////////////////////////////////////////////////////////////////////////// + #[oracle(avmOpcodeCall)] + fn call( + self, + gas: [Field; 3], // gas allocation: [l1Gas, l2Gas, daGas] + address: AztecAddress, + args: [Field; ARGS_COUNT], + // TODO(5110): consider passing in calldata directly + temporary_function_selector: Field + ) -> ([Field; RET_SIZE], u8) {} + // ^ return data ^ success + + //////////////////////////////////////////////////////////////////////////////// + // The functions below allow interface-equivalence with current public/private + //////////////////////////////////////////////////////////////////////////////// pub fn this_address(self) -> AztecAddress { self.address() } @@ -107,4 +119,25 @@ impl AVMContext { // Cannot nullify pending commitments in AVM, so `nullified_commitment` is not used self.emit_nullifier(nullifier); } + + pub fn call_public_function( + self: Self, + contract_address: AztecAddress, + temporary_function_selector: FunctionSelector, + args: [Field; ARGS_COUNT] + ) -> [Field; RET_SIZE] { + let gas = [/*l1Gas*/42, /*l2Gas*/24, /*daGas*/420]; + + let results = self.call( + gas, + contract_address, + args, + temporary_function_selector.to_field() + ); + let returnData: [Field; RET_SIZE] = results.0; + let success: u8 = results.1; + assert(success == 1, "Nested call failed!"); + + returnData + } } diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index f039bb367e3..102e09a5b66 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -2,6 +2,7 @@ contract AvmTest { // Libs use dep::aztec::state_vars::PublicMutable; use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH}; + use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; use dep::compressed_string::CompressedString; // avm lib @@ -214,4 +215,37 @@ contract AvmTest { fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) { context.message_portal(recipient, content) } + + // Directly call the external call opcode to initiate a nested call to the add function + #[aztec(public-vm)] + fn raw_nested_call_to_add(argA: Field, argB: Field) -> pub Field { + let selector = FunctionSelector::from_signature("avm_addArgsReturn(Field,Field)").to_field(); + let gas = [/*l1Gas*/42, /*l2Gas*/24, /*daGas*/420]; + + // Nested call + let results = context.call(gas, context.address(), [argA, argB], selector); + let returnData: [Field; 1] = results.0; + // this explicit size ^ is necessary to ensure that retSize is compile-time + // (ensure the returnData is in a HeapArray not a HeapVector) + let success: u8 = results.1; + + assert(success == 1, "Call failed"); + + let addResult = returnData[0]; + addResult + } + + // Use the `call_public_function` wrapper to initiate a nested call to the add function + #[aztec(public-vm)] + fn nested_call_to_add(argA: Field, argB: Field) -> pub Field { + let selector = FunctionSelector::from_signature("avm_addArgsReturn(Field,Field)"); + + // Nested call using standard context interface function + let returnData: [Field; 1] = context.call_public_function(context.address(), selector, [argA, argB]); + // this explicit size ^ is necessary to ensure that retSize is compile-time + // (ensure the returnData is in a HeapArray not a HeapVector) + + let addResult = returnData[0]; + addResult + } } diff --git a/yarn-project/simulator/src/avm/avm_context.ts b/yarn-project/simulator/src/avm/avm_context.ts index 1a20c101ce7..3ae5e6dfe3c 100644 --- a/yarn-project/simulator/src/avm/avm_context.ts +++ b/yarn-project/simulator/src/avm/avm_context.ts @@ -1,4 +1,4 @@ -import { AztecAddress } from '@aztec/circuits.js'; +import { AztecAddress, FunctionSelector } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; import { AvmExecutionEnvironment } from './avm_execution_environment.js'; @@ -35,8 +35,16 @@ export class AvmContext { * @param calldata - Data/arguments for nested call * @returns new AvmContext instance */ - public createNestedContractCallContext(address: AztecAddress, calldata: Fr[]): AvmContext { - const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedCall(address, calldata); + public createNestedContractCallContext( + address: AztecAddress, + calldata: Fr[], + temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(), + ): AvmContext { + const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedCall( + address, + calldata, + temporaryFunctionSelector, + ); const forkedWorldState = this.persistableState.fork(); const machineState = AvmMachineState.fromState(this.machineState); return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState); @@ -54,8 +62,16 @@ export class AvmContext { * @param calldata - Data/arguments for nested call * @returns new AvmContext instance */ - public createNestedContractStaticCallContext(address: AztecAddress, calldata: Fr[]): AvmContext { - const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedStaticCall(address, calldata); + public createNestedContractStaticCallContext( + address: AztecAddress, + calldata: Fr[], + temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(), + ): AvmContext { + const newExecutionEnvironment = this.environment.deriveEnvironmentForNestedStaticCall( + address, + calldata, + temporaryFunctionSelector, + ); const forkedWorldState = this.persistableState.fork(); const machineState = AvmMachineState.fromState(this.machineState); return new AvmContext(forkedWorldState, newExecutionEnvironment, machineState); diff --git a/yarn-project/simulator/src/avm/avm_execution_environment.ts b/yarn-project/simulator/src/avm/avm_execution_environment.ts index 1317cc71fdd..23340dd42b8 100644 --- a/yarn-project/simulator/src/avm/avm_execution_environment.ts +++ b/yarn-project/simulator/src/avm/avm_execution_environment.ts @@ -36,12 +36,19 @@ export class AvmExecutionEnvironment { public readonly calldata: Fr[], + // Function selector is temporary since eventually public contract bytecode will be one blob + // containing all functions, and function selector will become an application-level mechanism + // (e.g. first few bytes of calldata + compiler-generated jump table) public readonly temporaryFunctionSelector: FunctionSelector, ) {} - public deriveEnvironmentForNestedCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { + public deriveEnvironmentForNestedCall( + address: AztecAddress, + calldata: Fr[], + temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(), + ): AvmExecutionEnvironment { return new AvmExecutionEnvironment( - /*address=*/ address, + address, /*storageAddress=*/ address, this.origin, this.sender, @@ -53,14 +60,18 @@ export class AvmExecutionEnvironment { this.globals, this.isStaticCall, this.isDelegateCall, - /*calldata=*/ calldata, - this.temporaryFunctionSelector, + calldata, + temporaryFunctionSelector, ); } - public deriveEnvironmentForNestedStaticCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { + public deriveEnvironmentForNestedStaticCall( + address: AztecAddress, + calldata: Fr[], + temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(), + ): AvmExecutionEnvironment { return new AvmExecutionEnvironment( - /*address=*/ address, + address, /*storageAddress=*/ address, this.origin, this.sender, @@ -72,14 +83,18 @@ export class AvmExecutionEnvironment { this.globals, /*isStaticCall=*/ true, this.isDelegateCall, - /*calldata=*/ calldata, - this.temporaryFunctionSelector, + calldata, + temporaryFunctionSelector, ); } - public newDelegateCall(address: AztecAddress, calldata: Fr[]): AvmExecutionEnvironment { + public newDelegateCall( + address: AztecAddress, + calldata: Fr[], + temporaryFunctionSelector: FunctionSelector = FunctionSelector.empty(), + ): AvmExecutionEnvironment { return new AvmExecutionEnvironment( - /*address=*/ address, + address, this.storageAddress, this.origin, this.sender, @@ -91,8 +106,8 @@ export class AvmExecutionEnvironment { this.globals, this.isStaticCall, /*isDelegateCall=*/ true, - /*calldata=*/ calldata, - this.temporaryFunctionSelector, + calldata, + temporaryFunctionSelector, ); } } diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 47ac474f328..f74a9356371 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -394,6 +394,38 @@ describe('AVM simulator', () => { }); }); + describe('Test nested external calls from noir contract', () => { + it(`Should execute contract function that makes a nested call`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('avm_raw_nested_call_to_add'); + const addBytecode = getAvmTestContractBytecode('avm_addArgsReturn'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(addBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); + + it(`Should execute contract function that makes a nested call through the old interface`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('avm_nested_call_to_add'); + const addBytecode = getAvmTestContractBytecode('avm_addArgsReturn'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(addBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); + }); + describe('Storage accesses', () => { it('Should set a single value in storage', async () => { // We are setting the owner diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts index b6278c0dfff..1df77f9d0a6 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts @@ -39,6 +39,7 @@ describe('External Calls', () => { ...Buffer.from('d2345678', 'hex'), // retOffset ...Buffer.from('e2345678', 'hex'), // retSize ...Buffer.from('f2345678', 'hex'), // successOffset + ...Buffer.from('f3345678', 'hex'), // temporaryFunctionSelectorOffset ]); const inst = new Call( /*indirect=*/ 0x01, @@ -49,6 +50,7 @@ describe('External Calls', () => { /*retOffset=*/ 0xd2345678, /*retSize=*/ 0xe2345678, /*successOffset=*/ 0xf2345678, + /*temporaryFunctionSelectorOffset=*/ 0xf3345678, ); expect(Call.deserialize(buf)).toEqual(inst); @@ -89,6 +91,7 @@ describe('External Calls', () => { retOffset, retSize, successOffset, + /*temporaryFunctionSelectorOffset=*/ 0, ); await instruction.execute(context); @@ -123,6 +126,7 @@ describe('External Calls', () => { ...Buffer.from('d2345678', 'hex'), // retOffset ...Buffer.from('e2345678', 'hex'), // retSize ...Buffer.from('f2345678', 'hex'), // successOffset + ...Buffer.from('f3345678', 'hex'), // temporaryFunctionSelectorOffset ]); const inst = new StaticCall( /*indirect=*/ 0x01, @@ -133,6 +137,7 @@ describe('External Calls', () => { /*retOffset=*/ 0xd2345678, /*retSize=*/ 0xe2345678, /*successOffset=*/ 0xf2345678, + /*temporaryFunctionSelectorOffset=*/ 0xf3345678, ); expect(StaticCall.deserialize(buf)).toEqual(inst); @@ -176,6 +181,7 @@ describe('External Calls', () => { retOffset, retSize, successOffset, + /*temporaryFunctionSelectorOffset=*/ 0, ); await instruction.execute(context); diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index f491777d651..0c1f5abdc0b 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.ts @@ -1,9 +1,10 @@ -import { Fr } from '@aztec/circuits.js'; +import { FunctionSelector } from '@aztec/circuits.js'; import type { AvmContext } from '../avm_context.js'; import { Field, Uint8 } from '../avm_memory_types.js'; import { AvmSimulator } from '../avm_simulator.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; export class Call extends Instruction { @@ -20,6 +21,8 @@ export class Call extends Instruction { OperandType.UINT32, OperandType.UINT32, OperandType.UINT32, + /* temporary function selector */ + OperandType.UINT32, ]; constructor( @@ -31,16 +34,30 @@ export class Call extends Instruction { private retOffset: number, private retSize: number, private successOffset: number, + // Function selector is temporary since eventually public contract bytecode will be one blob + // containing all functions, and function selector will become an application-level mechanism + // (e.g. first few bytes of calldata + compiler-generated jump table) + private temporaryFunctionSelectorOffset: number, ) { super(); } // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3992): there is no concept of remaining / available gas at this moment async execute(context: AvmContext): Promise { - const callAddress = context.machineState.memory.getAs(this.addrOffset); - const calldata = context.machineState.memory.getSlice(this.argsOffset, this.argsSize).map(f => f.toFr()); + const [_gasOffset, addrOffset, argsOffset, retOffset, successOffset] = Addressing.fromWire(this.indirect).resolve( + [this._gasOffset, this.addrOffset, this.argsOffset, this.retOffset, this.successOffset], + context.machineState.memory, + ); + + const callAddress = context.machineState.memory.getAs(addrOffset); + const calldata = context.machineState.memory.getSlice(argsOffset, this.argsSize).map(f => f.toFr()); + const functionSelector = context.machineState.memory.getAs(this.temporaryFunctionSelectorOffset).toFr(); - const nestedContext = context.createNestedContractCallContext(callAddress.toFr(), calldata); + const nestedContext = context.createNestedContractCallContext( + callAddress.toFr(), + calldata, + FunctionSelector.fromField(functionSelector), + ); const nestedCallResults = await new AvmSimulator(nestedContext).execute(); const success = !nestedCallResults.reverted; @@ -50,8 +67,8 @@ export class Call extends Instruction { const convertedReturnData = returnData.map(f => new Field(f)); // Write our return data into memory - context.machineState.memory.set(this.successOffset, new Uint8(success ? 1 : 0)); - context.machineState.memory.setSlice(this.retOffset, convertedReturnData); + context.machineState.memory.set(successOffset, new Uint8(success ? 1 : 0)); + context.machineState.memory.setSlice(retOffset, convertedReturnData); if (success) { context.persistableState.acceptNestedCallState(nestedContext.persistableState); @@ -77,6 +94,8 @@ export class StaticCall extends Instruction { OperandType.UINT32, OperandType.UINT32, OperandType.UINT32, + /* temporary function selector */ + OperandType.UINT32, ]; constructor( @@ -88,17 +107,26 @@ export class StaticCall extends Instruction { private retOffset: number, private retSize: number, private successOffset: number, + private temporaryFunctionSelectorOffset: number, ) { super(); } async execute(context: AvmContext): Promise { - const callAddress = context.machineState.memory.get(this.addrOffset); - const calldata = context.machineState.memory - .getSlice(this.argsOffset, this.argsSize) - .map(f => new Fr(f.toBigInt())); + const [_gasOffset, addrOffset, argsOffset, retOffset, successOffset] = Addressing.fromWire(this.indirect).resolve( + [this._gasOffset, this.addrOffset, this.argsOffset, this.retOffset, this.successOffset], + context.machineState.memory, + ); + + const callAddress = context.machineState.memory.get(addrOffset); + const calldata = context.machineState.memory.getSlice(argsOffset, this.argsSize).map(f => f.toFr()); + const functionSelector = context.machineState.memory.getAs(this.temporaryFunctionSelectorOffset).toFr(); - const nestedContext = context.createNestedContractStaticCallContext(callAddress.toFr(), calldata); + const nestedContext = context.createNestedContractStaticCallContext( + callAddress.toFr(), + calldata, + FunctionSelector.fromField(functionSelector), + ); const nestedCallResults = await new AvmSimulator(nestedContext).execute(); const success = !nestedCallResults.reverted; @@ -108,8 +136,8 @@ export class StaticCall extends Instruction { const convertedReturnData = returnData.map(f => new Field(f)); // Write our return data into memory - context.machineState.memory.set(this.successOffset, new Uint8(success ? 1 : 0)); - context.machineState.memory.setSlice(this.retOffset, convertedReturnData); + context.machineState.memory.set(successOffset, new Uint8(success ? 1 : 0)); + context.machineState.memory.setSlice(retOffset, convertedReturnData); if (success) { context.persistableState.acceptNestedCallState(nestedContext.persistableState); diff --git a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts index 26e75171b65..a9d8f08bec2 100644 --- a/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts +++ b/yarn-project/simulator/src/avm/serialization/bytecode_serialization.test.ts @@ -84,6 +84,7 @@ describe('Bytecode Serialization', () => { /*retOffset=*/ 0xd2345678, /*retSize=*/ 0xe2345678, /*successOffset=*/ 0xf2345678, + /*temporaryFunctionSelectorOffset=*/ 0xf3345678, ), new StaticCall( /*indirect=*/ 0x01, @@ -94,6 +95,7 @@ describe('Bytecode Serialization', () => { /*retOffset=*/ 0xd2345678, /*retSize=*/ 0xe2345678, /*successOffset=*/ 0xf2345678, + /*temporaryFunctionSelectorOffset=*/ 0xf3345678, ), ]; const bytecode = Buffer.concat(instructions.map(i => i.serialize())); @@ -117,6 +119,7 @@ describe('Bytecode Serialization', () => { /*retOffset=*/ 0xd2345678, /*retSize=*/ 0xe2345678, /*successOffset=*/ 0xf2345678, + /*temporaryFunctionSelectorOffset=*/ 0xf3345678, ), new StaticCall( /*indirect=*/ 0x01, @@ -127,6 +130,7 @@ describe('Bytecode Serialization', () => { /*retOffset=*/ 0xd2345678, /*retSize=*/ 0xe2345678, /*successOffset=*/ 0xf2345678, + /*temporaryFunctionSelectorOffset=*/ 0xf3345678, ), ];