Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(avm): implement avm state getter opcodes within noir contracts #4402

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)!;
Maddiaa0 marked this conversation as resolved.
Show resolved Hide resolved

// 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
Loading