Skip to content

Commit

Permalink
external static
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Mar 11, 2024
1 parent 8a41916 commit a8ed02a
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 5 deletions.
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

0 comments on commit a8ed02a

Please sign in to comment.