From 9f2a6eb80f796a9be1c9c5b6a143dc70e5ec3c43 Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:57:05 +0000 Subject: [PATCH] feat(avm): implement avm state getter opcodes within noir contracts (#4402) Part of https://github.com/AztecProtocol/aztec-packages/issues/4313 --- avm-transpiler/src/transpile.rs | 55 ++++++++++- .../src/avm/avm_simulator.test.ts | 95 ++++++++++++++++++- yarn-project/aztec-nr/aztec/src/avm.nr | 1 + .../aztec-nr/aztec/src/avm/context.nr | 50 ++++++++++ yarn-project/aztec-nr/aztec/src/lib.nr | 1 + .../contracts/avm_test_contract/src/main.nr | 76 ++++++++++++++- 6 files changed, 273 insertions(+), 5 deletions(-) create mode 100644 yarn-project/aztec-nr/aztec/src/avm.nr create mode 100644 yarn-project/aztec-nr/aztec/src/avm/context.nr diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 04af018da1d..4a9da4b8f65 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -1,7 +1,7 @@ use acvm::acir::brillig::Opcode as BrilligOpcode; use acvm::acir::circuit::brillig::Brillig; -use acvm::brillig_vm::brillig::{BinaryFieldOp, BinaryIntOp}; +use acvm::brillig_vm::brillig::{BinaryFieldOp, BinaryIntOp, ValueOrArray}; use crate::instructions::{ AvmInstruction, AvmOperand, AvmTypeTag, FIRST_OPERAND_INDIRECT, ZEROTH_OPERAND_INDIRECT, @@ -252,7 +252,10 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { ], ..Default::default() }); - } + }, + BrilligOpcode::ForeignCall { function, destinations, inputs } => { + handle_foreign_call(&mut avm_instrs, function, destinations, inputs); + }, _ => panic!( "Transpiler doesn't know how to process {:?} brillig instruction", brillig_instr @@ -270,6 +273,54 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec { bytecode } +/// Handle foreign function calls +/// - Environment getting opcodes will be represented as foreign calls +/// - TODO: support for avm external calls through this function +fn handle_foreign_call( + avm_instrs: &mut Vec, + function: &String, + destinations: &Vec, + inputs: &Vec, +) { + // For the foreign calls we want to handle, we do not want inputs, as they are getters + assert!(inputs.len() == 0); + assert!(destinations.len() == 1); + let dest_offset_maybe = destinations[0]; + let dest_offset = match dest_offset_maybe { + ValueOrArray::MemoryAddress(dest_offset) => dest_offset.0, + _ => panic!("ForeignCall address destination should be a single value"), + }; + + let opcode = match function.as_str() { + "address" => AvmOpcode::ADDRESS, + "storageAddress" => AvmOpcode::STORAGEADDRESS, + "origin" => AvmOpcode::ORIGIN, + "sender" => AvmOpcode::SENDER, + "portal" => AvmOpcode::PORTAL, + "feePerL1Gas" => AvmOpcode::FEEPERL1GAS, + "feePerL2Gas" => AvmOpcode::FEEPERL2GAS, + "feePerDaGas" => AvmOpcode::FEEPERDAGAS, + "chainId" => AvmOpcode::CHAINID, + "version" => AvmOpcode::VERSION, + "blockNumber" => AvmOpcode::BLOCKNUMBER, + "timestamp" => AvmOpcode::TIMESTAMP, + // "callStackDepth" => AvmOpcode::CallStackDepth, + _ => panic!( + "Transpiler doesn't know how to process ForeignCall function {:?}", + function + ), + }; + + avm_instrs.push(AvmInstruction { + opcode, + indirect: Some(0), + operands: vec![AvmOperand::U32 { + value: dest_offset as u32, + }], + ..Default::default() + }); +} + /// Compute an array that maps each Brillig pc to an AVM pc. /// This must be done before transpiling to properly transpile jump destinations. /// This is necessary for two reasons: diff --git a/yarn-project/acir-simulator/src/avm/avm_simulator.test.ts b/yarn-project/acir-simulator/src/avm/avm_simulator.test.ts index 62a2c1ab56d..e2231ede872 100644 --- a/yarn-project/acir-simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/acir-simulator/src/avm/avm_simulator.test.ts @@ -1,3 +1,5 @@ +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { AvmTestContractArtifact } from '@aztec/noir-contracts'; @@ -5,7 +7,7 @@ import { jest } from '@jest/globals'; import { TypeTag } from './avm_memory_types.js'; import { AvmSimulator } from './avm_simulator.js'; -import { initContext, initExecutionEnvironment } from './fixtures/index.js'; +import { initContext, initExecutionEnvironment, initGlobalVariables } from './fixtures/index.js'; import { Add, CalldataCopy, Return } from './opcodes/index.js'; import { encodeToBytecode } from './serialization/bytecode_serialization.js'; @@ -54,5 +56,96 @@ describe('avm', () => { expect(returnData.length).toBe(1); expect(returnData).toEqual([new Fr(3)]); }); + + describe('Test env getters from noir contract', () => { + const testEnvGetter = async (valueName: string, value: any, functionName: string, globalVar: boolean = false) => { + const getterArtifact = AvmTestContractArtifact.functions.find(f => f.name === functionName)!; + + // Execute + let overrides = {}; + if (globalVar === true) { + const globals = initGlobalVariables({ [valueName]: value }); + overrides = { globals }; + } else { + overrides = { [valueName]: value }; + } + const context = initContext({ env: initExecutionEnvironment(overrides) }); + + // Decode bytecode into instructions + const bytecode = Buffer.from(getterArtifact.bytecode, 'base64'); + jest + .spyOn(context.worldState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(bytecode)); + // Execute + + const results = await new AvmSimulator(context).execute(); + + expect(results.reverted).toBe(false); + + const returnData = results.output; + expect(returnData.length).toBe(1); + expect(returnData).toEqual([value.toField()]); + }; + + it('address', async () => { + const address = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('address', address, 'avm_getAddress'); + }); + + it('storageAddress', async () => { + const storageAddress = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('storageAddress', storageAddress, 'avm_getStorageAddress'); + }); + + it('sender', async () => { + const sender = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('sender', sender, 'avm_getSender'); + }); + + it('origin', async () => { + const origin = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('origin', origin, 'avm_getOrigin'); + }); + + it('portal', async () => { + const portal = EthAddress.fromField(new Fr(1)); + await testEnvGetter('portal', portal, 'avm_getPortal'); + }); + + it('getFeePerL1Gas', async () => { + const fee = new Fr(1); + await testEnvGetter('feePerL1Gas', fee, 'avm_getFeePerL1Gas'); + }); + + it('getFeePerL2Gas', async () => { + const fee = new Fr(1); + await testEnvGetter('feePerL2Gas', fee, 'avm_getFeePerL2Gas'); + }); + + it('getFeePerDaGas', async () => { + const fee = new Fr(1); + await testEnvGetter('feePerDaGas', fee, 'avm_getFeePerDaGas'); + }); + + it('chainId', async () => { + const chainId = new Fr(1); + await testEnvGetter('chainId', chainId, 'avm_getChainId', /*globalVar=*/ true); + }); + + it('version', async () => { + const version = new Fr(1); + await testEnvGetter('version', version, 'avm_getVersion', /*globalVar=*/ true); + }); + + it('blockNumber', async () => { + const blockNumber = new Fr(1); + await testEnvGetter('blockNumber', blockNumber, 'avm_getBlockNumber', /*globalVar=*/ true); + }); + + it('timestamp', async () => { + const timestamp = new Fr(1); + await testEnvGetter('timestamp', timestamp, 'avm_getTimestamp', /*globalVar=*/ true); + }); + }); }); }); diff --git a/yarn-project/aztec-nr/aztec/src/avm.nr b/yarn-project/aztec-nr/aztec/src/avm.nr new file mode 100644 index 00000000000..3d9885db80d --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/avm.nr @@ -0,0 +1 @@ +mod context; diff --git a/yarn-project/aztec-nr/aztec/src/avm/context.nr b/yarn-project/aztec-nr/aztec/src/avm/context.nr new file mode 100644 index 00000000000..4e6d3f56f21 --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/avm/context.nr @@ -0,0 +1,50 @@ +use dep::protocol_types::address::{ + AztecAddress, + EthAddress, +}; + +// Getters that will be converted by the transpiler into their +// own opcodes +struct AvmContext {} + +// No new function as this struct is entirely static getters +impl AvmContext { + #[oracle(address)] + pub fn address() -> AztecAddress {} + + #[oracle(storageAddress)] + pub fn storage_address() -> AztecAddress {} + + #[oracle(origin)] + pub fn origin() -> AztecAddress {} + + #[oracle(sender)] + pub fn sender() -> AztecAddress {} + + #[oracle(portal)] + pub fn portal() -> EthAddress {} + + #[oracle(feePerL1Gas)] + pub fn fee_per_l1_gas() -> Field {} + + #[oracle(feePerL2Gas)] + pub fn fee_per_l2_gas() -> Field {} + + #[oracle(feePerDaGas)] + pub fn fee_per_da_gas() -> Field {} + + #[oracle(chainId)] + pub fn chain_id() -> Field {} + + #[oracle(version)] + pub fn version() -> Field {} + + #[oracle(blockNumber)] + pub fn block_number() -> Field {} + + #[oracle(timestamp)] + pub fn timestamp() -> Field {} + + // #[oracle(contractCallDepth)] + // pub fn contract_call_depth() -> Field {} +} diff --git a/yarn-project/aztec-nr/aztec/src/lib.nr b/yarn-project/aztec-nr/aztec/src/lib.nr index 7a8531601c4..88866fb2fbc 100644 --- a/yarn-project/aztec-nr/aztec/src/lib.nr +++ b/yarn-project/aztec-nr/aztec/src/lib.nr @@ -1,4 +1,5 @@ mod abi; +mod avm; mod context; mod hash; mod history; diff --git a/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr b/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr index 28dbf5576d0..107b9494046 100644 --- a/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -2,10 +2,14 @@ contract AvmTest { // Libs - use dep::aztec::protocol_types::{ - address::AztecAddress, + use dep::aztec::protocol_types::address::{ + AztecAddress, + EthAddress, }; + // avm lib + use dep::aztec::avm::context::AvmContext; + #[aztec(private)] fn constructor() {} @@ -15,6 +19,74 @@ contract AvmTest { argA + argB } + /************************************************************************ + * AvmContext functions + ************************************************************************/ + #[aztec(public-vm)] + fn getAddress() -> pub AztecAddress { + AvmContext::address() + } + + #[aztec(public-vm)] + fn getStorageAddress() -> pub AztecAddress { + AvmContext::storage_address() + } + + #[aztec(public-vm)] + fn getSender() -> pub AztecAddress { + AvmContext::sender() + } + + #[aztec(public-vm)] + fn getOrigin() -> pub AztecAddress { + AvmContext::origin() + } + + #[aztec(public-vm)] + fn getPortal() -> pub EthAddress { + AvmContext::portal() + } + + #[aztec(public-vm)] + fn getFeePerL1Gas() -> pub Field { + AvmContext::fee_per_l1_gas() + } + + #[aztec(public-vm)] + fn getFeePerL2Gas() -> pub Field { + AvmContext::fee_per_l2_gas() + } + + #[aztec(public-vm)] + fn getFeePerDaGas() -> pub Field { + AvmContext::fee_per_da_gas() + } + + #[aztec(public-vm)] + fn getChainId() -> pub Field { + AvmContext::chain_id() + } + + #[aztec(public-vm)] + fn getVersion() -> pub Field { + AvmContext::version() + } + + #[aztec(public-vm)] + fn getBlockNumber() -> pub Field { + AvmContext::block_number() + } + + #[aztec(public-vm)] + fn getTimestamp() -> pub Field { + AvmContext::timestamp() + } + + // #[aztec(public-vm)] + // fn getContractCallDepth() -> pub Field { + // AvmContext::contract_call_depth() + // } + // Function required for all contracts unconstrained fn compute_note_hash_and_nullifier( _contract_address: AztecAddress,