Skip to content

Commit

Permalink
feat(avm): implement avm state getter opcodes within noir contracts (#…
Browse files Browse the repository at this point in the history
…4402)

Part of #4313
  • Loading branch information
Maddiaa0 committed Feb 5, 2024
1 parent a6179bd commit 9f2a6eb
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 5 deletions.
55 changes: 53 additions & 2 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -252,7 +252,10 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
],
..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
Expand All @@ -270,6 +273,54 @@ pub fn brillig_to_avm(brillig: &Brillig) -> Vec<u8> {
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<AvmInstruction>,
function: &String,
destinations: &Vec<ValueOrArray>,
inputs: &Vec<ValueOrArray>,
) {
// 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:
Expand Down
95 changes: 94 additions & 1 deletion yarn-project/acir-simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
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';

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';

Expand Down Expand Up @@ -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);
});
});
});
});
1 change: 1 addition & 0 deletions yarn-project/aztec-nr/aztec/src/avm.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod context;
50 changes: 50 additions & 0 deletions yarn-project/aztec-nr/aztec/src/avm/context.nr
Original file line number Diff line number Diff line change
@@ -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 {}
}
1 change: 1 addition & 0 deletions yarn-project/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod abi;
mod avm;
mod context;
mod hash;
mod history;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}

Expand All @@ -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,
Expand Down

0 comments on commit 9f2a6eb

Please sign in to comment.