From 35fd8f0715ebeba400389ea993b22de80f762903 Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Fri, 8 Mar 2024 14:45:13 +0000 Subject: [PATCH] external static --- avm-transpiler/src/transpile.rs | 10 +++- .../aztec-nr/aztec/src/context/avm.nr | 31 +++++++++- .../contracts/avm_test_contract/src/main.nr | 40 +++++++++++++ .../simulator/src/avm/avm_simulator.test.ts | 59 ++++++++++++++++++- 4 files changed, 135 insertions(+), 5 deletions(-) diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 093d90a8711..c1123799dba 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -239,7 +239,10 @@ fn handle_foreign_call( inputs: &Vec, ) { match function { - "avmOpcodeCall" => handle_external_call(avm_instrs, destinations, inputs), + "avmOpcodeCall" => handle_external_call(avm_instrs, destinations, inputs, AvmOpcode::CALL), + "avmOpcodeStaticCall" => { + handle_external_call(avm_instrs, destinations, inputs, AvmOpcode::STATICCALL) + } "amvOpcodeEmitUnencryptedLog" => { handle_emit_unencrypted_log(avm_instrs, destinations, inputs) } @@ -280,10 +283,11 @@ fn handle_external_call( avm_instrs: &mut Vec, destinations: &Vec, inputs: &Vec, + opcode: AvmOpcode, ) { if destinations.len() != 2 || inputs.len() != 4 { panic!( - "Transpiler expects ForeignCall::CALL to have 2 destinations and 4 inputs, got {} and {}. + "Transpiler expects ForeignCall (Static)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() @@ -326,7 +330,7 @@ fn handle_external_call( _ => panic!("Call instruction's success destination should be a basic MemoryAddress",), }; avm_instrs.push(AvmInstruction { - opcode: AvmOpcode::CALL, + opcode: opcode, 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 }, diff --git a/noir-projects/aztec-nr/aztec/src/context/avm.nr b/noir-projects/aztec-nr/aztec/src/context/avm.nr index 2548c31c862..0aa883ffd17 100644 --- a/noir-projects/aztec-nr/aztec/src/context/avm.nr +++ b/noir-projects/aztec-nr/aztec/src/context/avm.nr @@ -82,7 +82,17 @@ impl AVMContext { pub fn send_l2_to_l1_msg(self, recipient: EthAddress, content: Field) {} #[oracle(avmOpcodeCall)] - fn call( + pub fn call( + self, + gas: [Field; 3], // gas allocation: [l1Gas, l2Gas, daGas] + address: AztecAddress, + args: [Field; ARGS_COUNT], + temporary_function_selector: Field + ) -> ([Field; RET_SIZE], u8) {} + // ^ return data ^ success + + #[oracle(avmOpcodeStaticCall)] + pub fn call_static( self, gas: [Field; 3], // gas allocation: [l1Gas, l2Gas, daGas] address: AztecAddress, @@ -139,4 +149,23 @@ impl AVMContext { returnData } + + pub fn static_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 (returnData, success): ([Field; RET_SIZE], u8) = self.call_static( + gas, + contract_address, + args, + temporary_function_selector.to_field() + ); + + assert(success == 1, "Nested static 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 633d0098349..eb1feef8589 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 @@ -246,4 +246,44 @@ contract AvmTest { let addResult = returnData[0]; addResult } + + // Directly call_static the external call opcode to initiate a nested call to the add function + #[aztec(public-vm)] + fn raw_nested_static_call_to_add(argA: Field, argB: Field) -> pub (Field, u8) { + let selector = FunctionSelector::from_signature("avm_addArgsReturn(Field,Field)").to_field(); + let gas = [/*l1Gas*/42, /*l2Gas*/24, /*daGas*/420]; + + let (resultData, success): ([Field; 1], u8) = context.call_static(gas, context.address(), [argA, argB], selector); + + (resultData[0], success) + } + + // Directly call_static setAdmin. Should fail since it's accessing storage. + #[aztec(public-vm)] + fn raw_nested_static_call_to_set_admin() -> pub u8 { + let selector = FunctionSelector::from_signature("avm_setAdmin()").to_field(); + let gas = [/*l1Gas*/42, /*l2Gas*/24, /*daGas*/420]; + + let (_returnData, success): ([Field; 0], u8) = context.call_static(gas, context.address(), [], selector); + + success + } + + // Indirectly call_static the external call opcode to initiate a nested call to the add function + #[aztec(public-vm)] + fn nested_static_call_to_add(argA: Field, argB: Field) -> pub Field { + let selector = FunctionSelector::from_signature("avm_addArgsReturn(Field,Field)"); + + let resultData: [Field; 1] = context.static_call_public_function(context.address(), selector, [argA, argB]); + + resultData[0] + } + + // Indirectly call_static setAdmin. Should revert since it's accessing storage. + #[aztec(public-vm)] + fn nested_static_call_to_set_admin() { + let selector = FunctionSelector::from_signature("avm_setAdmin()"); + + let _resultData: [Field; 0] = context.static_call_public_function(context.address(), selector, []); + } } diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 2388900df7f..2f57c0ff514 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -24,7 +24,7 @@ function getAvmTestContractBytecode(functionName: string): Buffer { const artifact = AvmTestContractArtifact.functions.find(f => f.name === functionName)!; assert( !!artifact?.bytecode, - `No bytecode found for function ${functionName}. Try re-running bootstraph.sh on the repository root.`, + `No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`, ); return Buffer.from(artifact.bytecode, 'base64'); } @@ -421,6 +421,63 @@ describe('AVM simulator', () => { expect(results.reverted).toBe(false); expect(results.output).toEqual([new Fr(3)]); }); + + it(`Should execute contract function that makes a nested static call`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('avm_raw_nested_static_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([/*result=*/ new Fr(3), /*success=*/ new Fr(1)]); + }); + + it(`Should execute contract function that makes a nested static call which modifies storage`, async () => { + const callBytecode = getAvmTestContractBytecode('avm_raw_nested_static_call_to_set_admin'); + const nestedBytecode = getAvmTestContractBytecode('avm_setAdmin'); + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(nestedBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(false); // The outer call should not revert. + expect(results.output).toEqual([new Fr(0)]); // The inner call should have reverted. + }); + + it(`Should execute contract function that makes a nested static call (old interface)`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('avm_nested_static_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([/*result=*/ new Fr(3)]); + }); + + it(`Should execute contract function that makes a nested static call which modifies storage (old interface)`, async () => { + const callBytecode = getAvmTestContractBytecode('avm_nested_static_call_to_set_admin'); + const nestedBytecode = getAvmTestContractBytecode('avm_setAdmin'); + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(nestedBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(true); // The outer call should revert. + }); }); describe('Storage accesses', () => {