Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarreiro committed Mar 11, 2024
1 parent 9ca41ef commit f91d4b6
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 153 deletions.
33 changes: 13 additions & 20 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,38 +890,35 @@ fn handle_black_box_function(avm_instrs: &mut Vec<AvmInstruction>, operation: &B
),
}
}

/// Emit a storage write opcode
/// The current implementation writes an array of values into storage ( contiguous slots in memory )
fn handle_storage_write(
avm_instrs: &mut Vec<AvmInstruction>,
destinations: &Vec<ValueOrArray>,
inputs: &Vec<ValueOrArray>,
) {
assert!(inputs.len() == 2);
assert!(destinations.len() == 1);
assert!(destinations.is_empty());

let slot_offset_maybe = inputs[0];
let slot_offset = match slot_offset_maybe {
ValueOrArray::MemoryAddress(slot_offset) => slot_offset.0,
_ => panic!("ForeignCall address destination should be a single value"),
_ => panic!("Storage write address destination should be a single value"),
};

let src_offset_maybe = inputs[1];
let (src_offset, src_size) = match src_offset_maybe {
ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0, size),
_ => panic!("Storage write address inputs should be an array of values"),
let src_offset = match src_offset_maybe {
ValueOrArray::MemoryAddress(offset) => offset.0,
_ => panic!("Storage write address inputs should be a single value"),
};

avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::SSTORE,
indirect: Some(ZEROTH_OPERAND_INDIRECT),
indirect: Some(ALL_DIRECT),
operands: vec![
AvmOperand::U32 {
value: src_offset as u32,
},
AvmOperand::U32 {
value: src_size as u32,
},
AvmOperand::U32 {
value: slot_offset as u32,
},
Expand All @@ -937,32 +934,28 @@ fn handle_storage_read(
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() == 2); // output, len - but we dont use this len - its for the oracle
assert!(inputs.len() == 1);
assert!(destinations.len() == 1);

let slot_offset_maybe = inputs[0];
let slot_offset = match slot_offset_maybe {
ValueOrArray::MemoryAddress(slot_offset) => slot_offset.0,
_ => panic!("ForeignCall address destination should be a single value"),
_ => panic!("Storage read address destination should be a single value"),
};

let dest_offset_maybe = destinations[0];
let (dest_offset, src_size) = match dest_offset_maybe {
ValueOrArray::HeapArray(HeapArray { pointer, size }) => (pointer.0, size),
_ => panic!("Storage write address inputs should be an array of values"),
let dest_offset = match dest_offset_maybe {
ValueOrArray::MemoryAddress(slot_offset) => slot_offset.0,
_ => panic!("Storage read address inputs should be a single value"),
};

avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::SLOAD,
indirect: Some(SECOND_OPERAND_INDIRECT),
indirect: Some(ALL_DIRECT),
operands: vec![
AvmOperand::U32 {
value: slot_offset as u32,
},
AvmOperand::U32 {
value: src_size as u32,
},
AvmOperand::U32 {
value: dest_offset as u32,
},
Expand Down
24 changes: 17 additions & 7 deletions noir-projects/aztec-nr/aztec/src/oracle/storage.nr
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
use dep::protocol_types::traits::{Deserialize, Serialize};

#[oracle(storageRead)]
fn storage_read_oracle<N>(_storage_slot: Field, _number_of_elements: Field) -> [Field; N] {}
fn storage_read_oracle(_storage_slot: Field) -> Field {}

unconstrained fn storage_read_oracle_wrapper<N>(_storage_slot: Field) -> [Field; N] {
storage_read_oracle(_storage_slot, N)
unconstrained fn storage_read_oracle_wrapper(storage_slot: Field) -> Field {
storage_read_oracle(storage_slot)
}

pub fn storage_read<N>(storage_slot: Field) -> [Field; N] {
storage_read_oracle_wrapper(storage_slot)
let mut ret: [Field; N] = [0; N];
for i in 0..N {
ret[i] = storage_read_oracle_wrapper(storage_slot + (i as Field));
}
ret
}

#[oracle(storageWrite)]
fn storage_write_oracle<N>(_storage_slot: Field, _values: [Field; N]) -> [Field; N] {}
fn storage_write_oracle(_storage_slot: Field, _value: Field) {}

unconstrained pub fn storage_write<N>(storage_slot: Field, fields: [Field; N]) {
let _hash = storage_write_oracle(storage_slot, fields);
unconstrained fn storage_write_oracle_wrapper(storage_slot: Field, value: Field) {
storage_write_oracle(storage_slot, value);
}

pub fn storage_write<N>(storage_slot: Field, fields: [Field; N]) {
for i in 0..N {
storage_write_oracle_wrapper(storage_slot + (i as Field), fields[i]);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
use dep::aztec::protocol_types::traits::{Serialize, Deserialize};

// struct TokenNote {
// amount: Field,
// owner: AztecAddress,
// }

// impl Serialize for TokenNote {
// fn serialize(&self) -> Vec<Field> {
// let mut serialized = Vec::new();
// serialized.push(self.amount);
// serialized.push(self.owner.serialize());
// serialized
// }
// }

struct Note {
a: Field,
b: Field,
}

impl Serialize<2> for Note {
fn serialize(self) -> [Field; 2] {
[self.a, self.b]
}
}

impl Deserialize<2> for Note {
fn deserialize(wire: [Field; 2]) -> Note {
Note {a: wire[0], b: wire[1]}
}
}

contract AvmTest {
use crate::Note;

// Libs
use dep::aztec::state_vars::PublicMutable;
use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH};
Expand All @@ -12,18 +47,35 @@ contract AvmTest {
fn constructor() {}

struct Storage {
owner: PublicMutable<AztecAddress>
single: PublicMutable<AztecAddress>,
list: PublicMutable<Note>,
}

#[aztec(public-vm)]
fn setStorageSingle() {
storage.single.write(context.sender());
}

#[aztec(public-vm)]
fn readStorageSingle() -> pub AztecAddress {
storage.single.read()
}

#[aztec(public-vm)]
fn setReadStorageSingle() -> pub AztecAddress {
storage.single.write(context.sender());
storage.single.read()
}

#[aztec(public-vm)]
fn setAdmin() {
storage.owner.write(context.sender());
fn setStorageList(a: Field, b: Field) {
storage.list.write(Note { a, b });
}

#[aztec(public-vm)]
fn setAndRead() -> pub AztecAddress {
storage.owner.write(context.sender());
storage.owner.read()
fn readStorageList() -> pub [Field; 2] {
let note: Note = storage.list.read();
[note.a, note.b]
}

#[aztec(public-vm)]
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/simulator/src/acvm/acvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ORACLE_NAMES } from './oracle/index.js';
*/
type ACIRCallback = Record<
ORACLE_NAMES,
(...args: ForeignCallInput[]) => ForeignCallOutput | Promise<ForeignCallOutput>
(...args: ForeignCallInput[]) => ForeignCallOutput | Promise<ForeignCallOutput> | Promise<void>
>;

/**
Expand Down Expand Up @@ -102,7 +102,8 @@ export async function acvm(
}

const result = await oracleFunction.call(callback, ...args);
return [result];
// void functions return undefined, which is mapped to an empty array.
return result === undefined ? [] : [result];
} catch (err) {
let typedError: Error;
if (err instanceof Error) {
Expand Down
11 changes: 5 additions & 6 deletions yarn-project/simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,13 @@ export class Oracle {
return toACVMField(portalContactAddress);
}

async storageRead([startStorageSlot]: ACVMField[], [numberOfElements]: ACVMField[]): Promise<ACVMField[]> {
const values = await this.typedOracle.storageRead(fromACVMField(startStorageSlot), +numberOfElements);
return values.map(toACVMField);
async storageRead([storageSlot]: ACVMField[]): Promise<ACVMField> {
const value = await this.typedOracle.storageRead(fromACVMField(storageSlot));
return toACVMField(value);
}

async storageWrite([startStorageSlot]: ACVMField[], values: ACVMField[]): Promise<ACVMField[]> {
const newValues = await this.typedOracle.storageWrite(fromACVMField(startStorageSlot), values.map(fromACVMField));
return newValues.map(toACVMField);
async storageWrite([storageSlot]: ACVMField[], [value]: ACVMField[]): Promise<void> {
await this.typedOracle.storageWrite(fromACVMField(storageSlot), fromACVMField(value));
}

emitEncryptedLog(
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ export abstract class TypedOracle {
throw new Error('Not available.');
}

storageRead(_startStorageSlot: Fr, _numberOfElements: number): Promise<Fr[]> {
storageRead(_storageSlot: Fr): Promise<Fr> {
throw new Error('Not available.');
}

storageWrite(_startStorageSlot: Fr, _values: Fr[]): Promise<Fr[]> {
storageWrite(_storageSlot: Fr, _value: Fr): Promise<void> {
throw new Error('Not available.');
}

Expand Down
35 changes: 30 additions & 5 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ describe('AVM simulator', () => {

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 nestedBytecode = getAvmTestContractBytecode('avm_setStorageSingle');
const context = initContext();
jest
.spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode')
Expand Down Expand Up @@ -468,7 +468,7 @@ describe('AVM simulator', () => {

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 nestedBytecode = getAvmTestContractBytecode('avm_setStorageSingle');
const context = initContext();
jest
.spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode')
Expand All @@ -490,7 +490,7 @@ describe('AVM simulator', () => {
const context = initContext({
env: initExecutionEnvironment({ sender, address, storageAddress: address }),
});
const bytecode = getAvmTestContractBytecode('avm_setAdmin');
const bytecode = getAvmTestContractBytecode('avm_setStorageSingle');
const results = await new AvmSimulator(context).executeBytecode(bytecode);

// Get contract function artifact
Expand All @@ -509,15 +509,15 @@ describe('AVM simulator', () => {
expect(slotTrace).toEqual([sender.toField()]);
});

it('Should read a value from storage', async () => {
it('Should set and read a value from storage', async () => {
// We are setting the owner
const sender = AztecAddress.fromField(new Fr(1));
const address = AztecAddress.fromField(new Fr(420));

const context = initContext({
env: initExecutionEnvironment({ sender, address, storageAddress: address }),
});
const bytecode = getAvmTestContractBytecode('avm_setAndRead');
const bytecode = getAvmTestContractBytecode('avm_setReadStorageSingle');
const results = await new AvmSimulator(context).executeBytecode(bytecode);

expect(results.reverted).toBe(false);
Expand All @@ -536,6 +536,31 @@ describe('AVM simulator', () => {
const slotWriteTrace = storageWriteTrace.get(1n);
expect(slotWriteTrace).toEqual([sender.toField()]);
});

it('Should set multiple values in storage', async () => {
const slot = 2n;
const sender = AztecAddress.fromField(new Fr(1));
const address = AztecAddress.fromField(new Fr(420));
const calldata = [new Fr(1), new Fr(2)];

const context = initContext({
env: initExecutionEnvironment({ sender, address, calldata, storageAddress: address }),
});
const bytecode = getAvmTestContractBytecode('avm_setStorageList');
const results = await new AvmSimulator(context).executeBytecode(bytecode);

expect(results.reverted).toBe(false);

const worldState = context.persistableState.flush();
const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!;
expect(storageSlot.get(slot)).toEqual(calldata[0]);
expect(storageSlot.get(slot + 1n)).toEqual(calldata[1]);

// Tracing
const storageTrace = worldState.storageWrites.get(address.toBigInt())!;
expect(storageTrace.get(slot)).toEqual([calldata[0]]);
expect(storageTrace.get(slot + 1n)).toEqual([calldata[1]]);
});
});
});
});
4 changes: 2 additions & 2 deletions yarn-project/simulator/src/avm/opcodes/external_calls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('External Calls', () => {
const successOffset = 7;
const otherContextInstructionsBytecode = encodeToBytecode([
new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0),
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*size=*/ 1, /*slotOffset=*/ 0),
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 0),
new Return(/*indirect=*/ 0, /*retOffset=*/ 0, /*size=*/ 2),
]);

Expand Down Expand Up @@ -163,7 +163,7 @@ describe('External Calls', () => {

const otherContextInstructions: Instruction[] = [
new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0),
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 1, /*size=*/ 1, /*slotOffset=*/ 0),
new SStore(/*indirect=*/ 0, /*srcOffset=*/ 1, /*slotOffset=*/ 0),
];

const otherContextInstructionsBytecode = encodeToBytecode(otherContextInstructions);
Expand Down
Loading

0 comments on commit f91d4b6

Please sign in to comment.