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-simulator): external static calls + integration #5089

Merged
merged 1 commit into from
Mar 11, 2024
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
10 changes: 7 additions & 3 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,10 @@ fn handle_foreign_call(
inputs: &Vec<ValueOrArray>,
) {
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)
}
Expand Down Expand Up @@ -280,10 +283,11 @@ fn handle_external_call(
avm_instrs: &mut Vec<AvmInstruction>,
destinations: &Vec<ValueOrArray>,
inputs: &Vec<ValueOrArray>,
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; <size>]`)!",
destinations.len(),
inputs.len()
Expand Down Expand Up @@ -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 },
Expand Down
31 changes: 30 additions & 1 deletion noir-projects/aztec-nr/aztec/src/context/avm.nr
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,17 @@ impl AVMContext {
pub fn send_l2_to_l1_msg(self, recipient: EthAddress, content: Field) {}

#[oracle(avmOpcodeCall)]
fn call<ARGS_COUNT, RET_SIZE>(
pub fn call<ARGS_COUNT, RET_SIZE>(
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<ARGS_COUNT, RET_SIZE>(
self,
gas: [Field; 3], // gas allocation: [l1Gas, l2Gas, daGas]
address: AztecAddress,
Expand Down Expand Up @@ -140,4 +150,23 @@ impl AVMContext {

returnData
}

pub fn static_call_public_function<ARGS_COUNT, RET_SIZE>(
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, []);
}
}
59 changes: 58 additions & 1 deletion yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -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', () => {
Expand Down
Loading