diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 19bd018ec71..c4402bb2642 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -435,7 +435,7 @@ fn handle_emit_unencrypted_log( destinations: &Vec, inputs: &Vec, ) { - if !destinations.is_empty() || inputs.len() != 2 { + if !destinations.is_empty() || inputs.len() != 3 { panic!( "Transpiler expects ForeignCall::EMITUNENCRYPTEDLOG to have 0 destinations and 3 inputs, got {} and {}", destinations.len(), @@ -449,23 +449,20 @@ fn handle_emit_unencrypted_log( inputs[0] ), }; - let (message_offset, message_size, message_offset_indirect) = match &inputs[1] { - ValueOrArray::HeapArray(array) => { - // Heap array, so offset to array is an indirect memory offset - (array.pointer.to_usize() as u32, array.size as u32, true) - } - ValueOrArray::MemoryAddress(single_val) => (single_val.to_usize() as u32, 1, false), + // The fields are a slice, and this is represented as a (length: Field, slice: HeapVector). + // The length field is redundant and we skipt it. + let (message_offset, message_size_offset) = match &inputs[2] { + ValueOrArray::HeapVector(vec) => (vec.pointer.to_usize() as u32, vec.size.0 as u32), _ => panic!("Unexpected inputs for ForeignCall::EMITUNENCRYPTEDLOG: {:?}", inputs), }; - let indirect_flag = if message_offset_indirect { FIRST_OPERAND_INDIRECT } else { 0 }; avm_instrs.push(AvmInstruction { opcode: AvmOpcode::EMITUNENCRYPTEDLOG, // The message array from Brillig is indirect. - indirect: Some(indirect_flag), + indirect: Some(FIRST_OPERAND_INDIRECT), operands: vec![ AvmOperand::U32 { value: event_offset }, AvmOperand::U32 { value: message_offset }, - AvmOperand::U32 { value: message_size }, + AvmOperand::U32 { value: message_size_offset }, ], ..Default::default() }); @@ -971,7 +968,7 @@ fn handle_storage_read( avm_instrs.push(AvmInstruction { opcode: AvmOpcode::SLOAD, - indirect: Some(SECOND_OPERAND_INDIRECT), + indirect: Some(FIRST_OPERAND_INDIRECT), operands: vec![ AvmOperand::U32 { value: slot_offset as u32 }, AvmOperand::U32 { value: src_size as u32 }, diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index 3318e4130b4..814471cf870 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -1,6 +1,6 @@ use crate::hash::{compute_secret_hash, compute_message_hash, compute_message_nullifier}; use dep::protocol_types::address::{AztecAddress, EthAddress}; -use dep::protocol_types::traits::{Deserialize, Empty}; +use dep::protocol_types::traits::{Serialize, Deserialize, Empty}; use dep::protocol_types::abis::function_selector::FunctionSelector; use crate::context::inputs::public_context_inputs::PublicContextInputs; use crate::context::gas::GasOpts; @@ -28,11 +28,17 @@ impl PublicContext { * * @param event_selector The event selector for the log. * @param message The message to emit in the log. - * Should be automatically convertible to [Field; N]. For example str works with - * one char per field. Otherwise you can use CompressedString. */ - pub fn emit_unencrypted_log_with_selector(&mut self, event_selector: Field, log: T) { - emit_unencrypted_log(event_selector, log); + pub fn emit_unencrypted_log_with_selector( + &mut self, + event_selector: Field, + log: T + ) where T: Serialize { + emit_unencrypted_log(event_selector, Serialize::serialize(log).as_slice()); + } + // For compatibility with the selector-less API. We'll probably rename the above one. + pub fn emit_unencrypted_log(&mut self, log: T) where T: Serialize { + self.emit_unencrypted_log_with_selector(/*event_selector=*/ 5, log); } pub fn note_hash_exists(self, note_hash: Field, leaf_index: Field) -> bool { note_hash_exists(note_hash, leaf_index) == 1 @@ -57,11 +63,6 @@ impl PublicContext { nullifier_exists(unsiloed_nullifier, address.to_field()) == 1 } - fn emit_unencrypted_log(&mut self, log: T) { - let event_selector = 5; // Matches current PublicContext. - self.emit_unencrypted_log_with_selector(event_selector, log); - } - fn consume_l1_to_l2_message( &mut self, content: Field, @@ -234,7 +235,7 @@ unconstrained fn nullifier_exists(nullifier: Field, address: Field) -> u8 { unconstrained fn emit_nullifier(nullifier: Field) { emit_nullifier_opcode(nullifier) } -unconstrained fn emit_unencrypted_log(event_selector: Field, message: T) { +unconstrained fn emit_unencrypted_log(event_selector: Field, message: [Field]) { emit_unencrypted_log_opcode(event_selector, message) } unconstrained fn l1_to_l2_msg_exists(msg_hash: Field, msg_leaf_index: Field) -> u8 { @@ -319,7 +320,7 @@ fn nullifier_exists_opcode(nullifier: Field, address: Field) -> u8 {} fn emit_nullifier_opcode(nullifier: Field) {} #[oracle(amvOpcodeEmitUnencryptedLog)] -fn emit_unencrypted_log_opcode(event_selector: Field, message: T) {} +fn emit_unencrypted_log_opcode(event_selector: Field, message: [Field]) {} #[oracle(avmOpcodeL1ToL2MsgExists)] fn l1_to_l2_msg_exists_opcode(msg_hash: Field, msg_leaf_index: Field) -> u8 {} @@ -367,4 +368,4 @@ impl FunctionReturns { pub fn deserialize_into(self) -> T where T: Deserialize { Deserialize::deserialize(self.raw()) } -} +} \ No newline at end of file diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index cb633e0dec3..befe4412f0b 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -300,10 +300,10 @@ contract AvmTest { #[aztec(public)] fn emit_unencrypted_log() { - context.emit_unencrypted_log_with_selector(/*event_selector=*/ 5, /*message=*/ [10, 20, 30]); - context.emit_unencrypted_log_with_selector(/*event_selector=*/ 8, /*message=*/ "Hello, world!"); + context.emit_unencrypted_log(/*message=*/ [10, 20, 30]); + context.emit_unencrypted_log(/*message=*/ "Hello, world!"); let s: CompressedString<2,44> = CompressedString::from_string("A long time ago, in a galaxy far far away..."); - context.emit_unencrypted_log_with_selector(/*event_selector=*/ 10, /*message=*/ s); + context.emit_unencrypted_log(/*message=*/ s); } #[aztec(public)] diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr index f94e574e7bd..5426201d3c0 100644 --- a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr @@ -32,7 +32,6 @@ contract StaticChild { fn pub_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); context.emit_unencrypted_log(new_value); - new_value } @@ -86,7 +85,6 @@ contract StaticChild { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); context.emit_unencrypted_log(new_value); - new_value } @@ -97,7 +95,6 @@ contract StaticChild { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); context.emit_unencrypted_log(new_value); - new_value } } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 344adb9491b..d1329c33dd0 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -343,11 +343,12 @@ contract Test { // docs:end:is-time-equal #[aztec(public)] - fn emit_unencrypted(value: Field) -> Field { + fn emit_unencrypted(value: Field) { // docs:start:emit_unencrypted - context.emit_unencrypted_log(value); + context.emit_unencrypted_log(/*message=*/ value); + context.emit_unencrypted_log(/*message=*/ [10, 20, 30]); + context.emit_unencrypted_log(/*message=*/ "Hello, world!"); // docs:end:emit_unencrypted - 0 } #[aztec(public)] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr index e4e2b1825ec..bce2078479a 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -90,6 +90,11 @@ trait Serialize { } // docs:end:serialize +impl Serialize for [Field; N] { + fn serialize(self) -> [Field; N] { + self + } +} impl Serialize for str { fn serialize(self) -> [Field; N] { let mut result = [0; N]; diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 92374003c48..d9c20f34210 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -347,10 +347,10 @@ describe('AVM simulator: transpiled Noir contracts', () => { ), new UnencryptedL2Log( context.environment.address, - new EventSelector(8), + new EventSelector(5), Buffer.concat(expectedString.map(f => f.toBuffer())), ), - new UnencryptedL2Log(context.environment.address, new EventSelector(10), expectedCompressedString), + new UnencryptedL2Log(context.environment.address, new EventSelector(5), expectedCompressedString), ]); }); diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts index 7934a880ad6..5f4ac1eae0d 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts @@ -6,7 +6,7 @@ import { mock } from 'jest-mock-extended'; import { type CommitmentsDB } from '../../index.js'; import { type AvmContext } from '../avm_context.js'; -import { Field, Uint8 } from '../avm_memory_types.js'; +import { Field, Uint8, Uint32 } from '../avm_memory_types.js'; import { InstructionExecutionError, StaticCallAlterationError } from '../errors.js'; import { initContext, initExecutionEnvironment, initHostStorage } from '../fixtures/index.js'; import { AvmPersistableStateManager } from '../journal/journal.js'; @@ -392,14 +392,14 @@ describe('Accrued Substate', () => { EmitUnencryptedLog.opcode, // opcode 0x01, // indirect ...Buffer.from('02345678', 'hex'), // event selector offset - ...Buffer.from('12345678', 'hex'), // offset - ...Buffer.from('a2345678', 'hex'), // length + ...Buffer.from('12345678', 'hex'), // log offset + ...Buffer.from('a2345678', 'hex'), // length offset ]); const inst = new EmitUnencryptedLog( /*indirect=*/ 0x01, /*eventSelectorOffset=*/ 0x02345678, /*offset=*/ 0x12345678, - /*length=*/ 0xa2345678, + /*lengthOffset=*/ 0xa2345678, ); expect(EmitUnencryptedLog.deserialize(buf)).toEqual(inst); @@ -410,16 +410,18 @@ describe('Accrued Substate', () => { const startOffset = 0; const eventSelector = 5; const eventSelectorOffset = 10; + const logSizeOffset = 20; const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; context.machineState.memory.setSlice(startOffset, values); context.machineState.memory.set(eventSelectorOffset, new Field(eventSelector)); + context.machineState.memory.set(logSizeOffset, new Uint32(values.length)); await new EmitUnencryptedLog( /*indirect=*/ 0, eventSelectorOffset, /*offset=*/ startOffset, - values.length, + logSizeOffset, ).execute(context); const journalState = context.persistableState.flush(); @@ -472,11 +474,12 @@ describe('Accrued Substate', () => { it('All substate emission instructions should fail within a static call', async () => { context = initContext({ env: initExecutionEnvironment({ isStaticCall: true }) }); + context.machineState.memory.set(0, new Field(2020n)); const instructions = [ new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0), new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0), - new EmitUnencryptedLog(/*indirect=*/ 0, /*eventSelector=*/ 0, /*offset=*/ 0, /*logSize=*/ 1), + new EmitUnencryptedLog(/*indirect=*/ 0, /*eventSelector=*/ 0, /*offset=*/ 0, /*logSizeOffset=*/ 0), new SendL2ToL1Message(/*indirect=*/ 0, /*recipientOffset=*/ 0, /*contentOffset=*/ 1), ]; diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts index 820d937bd23..76cdbe236a2 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts @@ -1,5 +1,5 @@ import type { AvmContext } from '../avm_context.js'; -import { Uint8 } from '../avm_memory_types.js'; +import { TypeTag, Uint8 } from '../avm_memory_types.js'; import { InstructionExecutionError, StaticCallAlterationError } from '../errors.js'; import { NullifierCollisionError } from '../journal/nullifiers.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; @@ -31,17 +31,22 @@ export class NoteHashExists extends Instruction { const memoryOperations = { reads: 2, writes: 1, indirect: this.indirect }; const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); + const [noteHashOffset, leafIndexOffset, existsOffset] = Addressing.fromWire(this.indirect).resolve( + [this.noteHashOffset, this.leafIndexOffset, this.existsOffset], + memory, + ); + memory.checkTags(TypeTag.FIELD, noteHashOffset, leafIndexOffset); // Note that this instruction accepts any type in memory, and converts to Field. - const noteHash = memory.get(this.noteHashOffset).toFr(); - const leafIndex = memory.get(this.leafIndexOffset).toFr(); + const noteHash = memory.get(noteHashOffset).toFr(); + const leafIndex = memory.get(leafIndexOffset).toFr(); const exists = await context.persistableState.checkNoteHashExists( context.environment.storageAddress, noteHash, leafIndex, ); - memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0)); + memory.set(existsOffset, exists ? new Uint8(1) : new Uint8(0)); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -63,11 +68,14 @@ export class EmitNoteHash extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); + const [noteHashOffset] = Addressing.fromWire(this.indirect).resolve([this.noteHashOffset], memory); + memory.checkTag(TypeTag.FIELD, noteHashOffset); + if (context.environment.isStaticCall) { throw new StaticCallAlterationError(); } - const noteHash = memory.get(this.noteHashOffset).toFr(); + const noteHash = memory.get(noteHashOffset).toFr(); context.persistableState.writeNoteHash(context.environment.storageAddress, noteHash); memory.assert(memoryOperations); @@ -101,11 +109,17 @@ export class NullifierExists extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const nullifier = memory.get(this.nullifierOffset).toFr(); - const address = memory.get(this.addressOffset).toFr(); + const [nullifierOffset, addressOffset, existsOffset] = Addressing.fromWire(this.indirect).resolve( + [this.nullifierOffset, this.addressOffset, this.existsOffset], + memory, + ); + memory.checkTags(TypeTag.FIELD, nullifierOffset, addressOffset); + + const nullifier = memory.get(nullifierOffset).toFr(); + const address = memory.get(addressOffset).toFr(); const exists = await context.persistableState.checkNullifierExists(address, nullifier); - memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0)); + memory.set(existsOffset, exists ? new Uint8(1) : new Uint8(0)); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -131,7 +145,10 @@ export class EmitNullifier extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const nullifier = memory.get(this.nullifierOffset).toFr(); + const [nullifierOffset] = Addressing.fromWire(this.indirect).resolve([this.nullifierOffset], memory); + memory.checkTag(TypeTag.FIELD, nullifierOffset); + + const nullifier = memory.get(nullifierOffset).toFr(); try { await context.persistableState.writeNullifier(context.environment.storageAddress, nullifier); } catch (e) { @@ -176,10 +193,16 @@ export class L1ToL2MessageExists extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const msgHash = memory.get(this.msgHashOffset).toFr(); - const msgLeafIndex = memory.get(this.msgLeafIndexOffset).toFr(); + const [msgHashOffset, msgLeafIndexOffset, existsOffset] = Addressing.fromWire(this.indirect).resolve( + [this.msgHashOffset, this.msgLeafIndexOffset, this.existsOffset], + memory, + ); + memory.checkTags(TypeTag.FIELD, msgHashOffset, msgLeafIndexOffset); + + const msgHash = memory.get(msgHashOffset).toFr(); + const msgLeafIndex = memory.get(msgLeafIndexOffset).toFr(); const exists = await context.persistableState.checkL1ToL2MessageExists(msgHash, msgLeafIndex); - memory.set(this.existsOffset, exists ? new Uint8(1) : new Uint8(0)); + memory.set(existsOffset, exists ? new Uint8(1) : new Uint8(0)); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -202,7 +225,7 @@ export class EmitUnencryptedLog extends Instruction { private indirect: number, private eventSelectorOffset: number, private logOffset: number, - private logSize: number, + private logSizeOffset: number, ) { super(); } @@ -212,18 +235,24 @@ export class EmitUnencryptedLog extends Instruction { throw new StaticCallAlterationError(); } - const memoryOperations = { reads: 1 + this.logSize, indirect: this.indirect }; const memory = context.machineState.memory.track(this.type); - context.machineState.consumeGas(this.gasCost(memoryOperations)); - const [eventSelectorOffset, logOffset] = Addressing.fromWire(this.indirect).resolve( - [this.eventSelectorOffset, this.logOffset], + const [eventSelectorOffset, logOffset, logSizeOffset] = Addressing.fromWire(this.indirect).resolve( + [this.eventSelectorOffset, this.logOffset, this.logSizeOffset], memory, ); + memory.checkTag(TypeTag.FIELD, eventSelectorOffset); + // TODO: enable once Noir generates UINT32 + // memory.checkTag(TypeTag.UINT32, logSize); + const logSize = memory.get(logSizeOffset).toNumber(); + memory.checkTagsRange(TypeTag.FIELD, logOffset, logSize); const contractAddress = context.environment.address; const event = memory.get(eventSelectorOffset).toFr(); - const log = memory.getSlice(logOffset, this.logSize).map(f => f.toFr()); + + const memoryOperations = { reads: 2 + logSize, indirect: this.indirect }; + context.machineState.consumeGas(this.gasCost(memoryOperations)); + const log = memory.getSlice(logOffset, logSize).map(f => f.toFr()); context.persistableState.writeLog(contractAddress, event, log); memory.assert(memoryOperations); @@ -250,8 +279,13 @@ export class SendL2ToL1Message extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const recipient = memory.get(this.recipientOffset).toFr(); - const content = memory.get(this.contentOffset).toFr(); + const [recipientOffset, contentOffset] = Addressing.fromWire(this.indirect).resolve( + [this.recipientOffset, this.contentOffset], + memory, + ); + + const recipient = memory.get(recipientOffset).toFr(); + const content = memory.get(contentOffset).toFr(); context.persistableState.writeL1Message(recipient, content); memory.assert(memoryOperations); diff --git a/yarn-project/simulator/src/avm/opcodes/arithmetic.ts b/yarn-project/simulator/src/avm/opcodes/arithmetic.ts index 84e05071708..d918fbbd15c 100644 --- a/yarn-project/simulator/src/avm/opcodes/arithmetic.ts +++ b/yarn-project/simulator/src/avm/opcodes/arithmetic.ts @@ -1,6 +1,7 @@ import type { AvmContext } from '../avm_context.js'; import { type Field, type MemoryValue, TypeTag } from '../avm_memory_types.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; import { ThreeOperandInstruction } from './instruction_impl.js'; @@ -10,13 +11,17 @@ export abstract class ThreeOperandArithmeticInstruction extends ThreeOperandInst const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - memory.checkTags(this.inTag, this.aOffset, this.bOffset); + const [aOffset, bOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.aOffset, this.bOffset, this.dstOffset], + memory, + ); + memory.checkTags(this.inTag, aOffset, bOffset); - const a = memory.get(this.aOffset); - const b = memory.get(this.bOffset); + const a = memory.get(aOffset); + const b = memory.get(bOffset); const dest = this.compute(a, b); - memory.set(this.dstOffset, dest); + memory.set(dstOffset, dest); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -83,13 +88,17 @@ export class FieldDiv extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - memory.checkTags(TypeTag.FIELD, this.aOffset, this.bOffset); + const [aOffset, bOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.aOffset, this.bOffset, this.dstOffset], + memory, + ); + memory.checkTags(TypeTag.FIELD, aOffset, bOffset); - const a = memory.getAs(this.aOffset); - const b = memory.getAs(this.bOffset); + const a = memory.getAs(aOffset); + const b = memory.getAs(bOffset); const dest = a.fdiv(b); - memory.set(this.dstOffset, dest); + memory.set(dstOffset, dest); memory.assert(memoryOperations); context.machineState.incrementPc(); diff --git a/yarn-project/simulator/src/avm/opcodes/bitwise.ts b/yarn-project/simulator/src/avm/opcodes/bitwise.ts index ae8f69db372..e43f36550b0 100644 --- a/yarn-project/simulator/src/avm/opcodes/bitwise.ts +++ b/yarn-project/simulator/src/avm/opcodes/bitwise.ts @@ -1,6 +1,7 @@ import type { AvmContext } from '../avm_context.js'; import { type IntegralValue, type TaggedMemoryInterface, TypeTag } from '../avm_memory_types.js'; import { Opcode } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { ThreeOperandInstruction, TwoOperandInstruction } from './instruction_impl.js'; abstract class ThreeOperandBitwiseInstruction extends ThreeOperandInstruction { @@ -9,13 +10,17 @@ abstract class ThreeOperandBitwiseInstruction extends ThreeOperandInstruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - this.checkTags(memory, this.inTag, this.aOffset, this.bOffset); + const [aOffset, bOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.aOffset, this.bOffset, this.dstOffset], + memory, + ); + this.checkTags(memory, this.inTag, aOffset, bOffset); - const a = memory.getAs(this.aOffset); - const b = memory.getAs(this.bOffset); + const a = memory.getAs(aOffset); + const b = memory.getAs(bOffset); const res = this.compute(a, b); - memory.set(this.dstOffset, res); + memory.set(dstOffset, res); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -93,12 +98,12 @@ export class Not extends TwoOperandInstruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - memory.checkTags(this.inTag, this.aOffset); - - const a = memory.getAs(this.aOffset); + const [aOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.dstOffset], memory); + memory.checkTags(this.inTag, aOffset); + const a = memory.getAs(aOffset); const res = a.not(); - memory.set(this.dstOffset, res); + memory.set(dstOffset, res); memory.assert(memoryOperations); context.machineState.incrementPc(); diff --git a/yarn-project/simulator/src/avm/opcodes/comparators.ts b/yarn-project/simulator/src/avm/opcodes/comparators.ts index 919b7cf3431..c3e90da54c3 100644 --- a/yarn-project/simulator/src/avm/opcodes/comparators.ts +++ b/yarn-project/simulator/src/avm/opcodes/comparators.ts @@ -1,6 +1,7 @@ import type { AvmContext } from '../avm_context.js'; import { type MemoryValue, Uint8 } from '../avm_memory_types.js'; import { Opcode } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { ThreeOperandInstruction } from './instruction_impl.js'; abstract class ComparatorInstruction extends ThreeOperandInstruction { @@ -9,13 +10,17 @@ abstract class ComparatorInstruction extends ThreeOperandInstruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - memory.checkTags(this.inTag, this.aOffset, this.bOffset); + const [aOffset, bOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.aOffset, this.bOffset, this.dstOffset], + memory, + ); + memory.checkTags(this.inTag, aOffset, bOffset); - const a = memory.get(this.aOffset); - const b = memory.get(this.bOffset); + const a = memory.get(aOffset); + const b = memory.get(bOffset); const dest = new Uint8(this.compare(a, b) ? 1 : 0); - memory.set(this.dstOffset, dest); + memory.set(dstOffset, dest); memory.assert(memoryOperations); context.machineState.incrementPc(); diff --git a/yarn-project/simulator/src/avm/opcodes/contract.ts b/yarn-project/simulator/src/avm/opcodes/contract.ts index a0b41705bfd..42bb8935244 100644 --- a/yarn-project/simulator/src/avm/opcodes/contract.ts +++ b/yarn-project/simulator/src/avm/opcodes/contract.ts @@ -1,7 +1,7 @@ import { AztecAddress, Fr } from '@aztec/circuits.js'; import type { AvmContext } from '../avm_context.js'; -import { Field } from '../avm_memory_types.js'; +import { Field, TypeTag } from '../avm_memory_types.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; @@ -22,12 +22,17 @@ export class GetContractInstance extends Instruction { } async execute(context: AvmContext): Promise { + const memoryOperations = { reads: 1, writes: 6, indirect: this.indirect }; + const memory = context.machineState.memory.track(this.type); + context.machineState.consumeGas(this.gasCost(memoryOperations)); + const [addressOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( [this.addressOffset, this.dstOffset], - context.machineState.memory, + memory, ); + memory.checkTag(TypeTag.FIELD, addressOffset); - const address = AztecAddress.fromField(context.machineState.memory.get(addressOffset).toFr()); + const address = AztecAddress.fromField(memory.get(addressOffset).toFr()); const instance = await context.persistableState.hostStorage.contractsDb.getContractInstance(address); const data = @@ -49,8 +54,9 @@ export class GetContractInstance extends Instruction { instance.publicKeysHash, ].map(f => new Field(f)); - context.machineState.memory.setSlice(dstOffset, data); + memory.setSlice(dstOffset, data); + memory.assert(memoryOperations); context.machineState.incrementPc(); } } diff --git a/yarn-project/simulator/src/avm/opcodes/control_flow.ts b/yarn-project/simulator/src/avm/opcodes/control_flow.ts index 7c44322fa73..635430d6b83 100644 --- a/yarn-project/simulator/src/avm/opcodes/control_flow.ts +++ b/yarn-project/simulator/src/avm/opcodes/control_flow.ts @@ -2,6 +2,7 @@ import type { AvmContext } from '../avm_context.js'; import { type IntegralValue } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; export class Jump extends Instruction { @@ -44,7 +45,8 @@ export class JumpI extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const condition = memory.getAs(this.condOffset); + const [condOffset] = Addressing.fromWire(this.indirect).resolve([this.condOffset], memory); + const condition = memory.getAs(condOffset); // TODO: reconsider this casting if (condition.toBigInt() == 0n) { diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index 25ae01baa5b..75c71f61d7b 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.ts @@ -4,7 +4,7 @@ import { padArrayEnd } from '@aztec/foundation/collection'; import { convertAvmResultsToPxResult, createPublicExecution } from '../../public/transitional_adaptors.js'; import type { AvmContext } from '../avm_context.js'; import { gasLeftToGas } from '../avm_gas.js'; -import { Field, Uint8 } from '../avm_memory_types.js'; +import { Field, TypeTag, Uint8 } from '../avm_memory_types.js'; import { type AvmContractCallResults } from '../avm_message_call_result.js'; import { AvmSimulator } from '../avm_simulator.js'; import { RethrownError } from '../errors.js'; @@ -53,9 +53,16 @@ abstract class ExternalCall extends Instruction { [this.gasOffset, this.addrOffset, this.argsOffset, this.argsSizeOffset, this.retOffset, this.successOffset], memory, ); + memory.checkTags(TypeTag.FIELD, gasOffset, gasOffset + 1); + memory.checkTag(TypeTag.FIELD, addrOffset); + // TODO: Enable when Noir uses UINT32 + // memory.checkTag(TypeTag.UINT32, argsSizeOffset); + memory.checkTag(TypeTag.FIELD, this.functionSelectorOffset); - const callAddress = memory.getAs(addrOffset); const calldataSize = memory.get(argsSizeOffset).toNumber(); + memory.checkTagsRange(TypeTag.FIELD, argsOffset, calldataSize); + + const callAddress = memory.getAs(addrOffset); const calldata = memory.getSlice(argsOffset, calldataSize).map(f => f.toFr()); const functionSelector = memory.getAs(this.functionSelectorOffset).toFr(); // If we are already in a static call, we propagate the environment. diff --git a/yarn-project/simulator/src/avm/opcodes/hashing.ts b/yarn-project/simulator/src/avm/opcodes/hashing.ts index 7bb53eb60ae..2c586d1227b 100644 --- a/yarn-project/simulator/src/avm/opcodes/hashing.ts +++ b/yarn-project/simulator/src/avm/opcodes/hashing.ts @@ -1,7 +1,7 @@ import { keccak256, pedersenHash, poseidon2Permutation, sha256 } from '@aztec/foundation/crypto'; import { type AvmContext } from '../avm_context.js'; -import { Field, Uint8 } from '../avm_memory_types.js'; +import { Field, TypeTag, Uint8 } from '../avm_memory_types.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; @@ -32,6 +32,7 @@ export class Poseidon2 extends Instruction { [this.inputStateOffset, this.outputStateOffset], memory, ); + memory.checkTagsRange(TypeTag.FIELD, inputOffset, Poseidon2.stateSize); const inputState = memory.getSlice(inputOffset, Poseidon2.stateSize); const outputState = poseidon2Permutation(inputState); @@ -74,10 +75,14 @@ export class Keccak extends Instruction { [this.dstOffset, this.messageOffset, this.messageSizeOffset], memory, ); + // TODO: Enable when Noir uses UINT32 + // memory.checkTag(TypeTag.UINT32, messageSizeOffset); const messageSize = memory.get(messageSizeOffset).toNumber(); const memoryOperations = { reads: messageSize + 1, writes: 32, indirect: this.indirect }; context.machineState.consumeGas(this.gasCost(memoryOperations)); + memory.checkTagsRange(TypeTag.UINT8, messageOffset, messageSize); + const messageData = Buffer.concat(memory.getSlice(messageOffset, messageSize).map(word => word.toBuffer())); const hashBuffer = keccak256(messageData); @@ -119,9 +124,12 @@ export class Sha256 extends Instruction { [this.dstOffset, this.messageOffset, this.messageSizeOffset], memory, ); + // TODO: Enable when Noir uses UINT32 + // memory.checkTag(TypeTag.UINT32, messageSizeOffset); const messageSize = memory.get(messageSizeOffset).toNumber(); const memoryOperations = { reads: messageSize + 1, writes: 32, indirect: this.indirect }; context.machineState.consumeGas(this.gasCost(memoryOperations)); + memory.checkTagsRange(TypeTag.UINT8, messageOffset, messageSize); const messageData = Buffer.concat(memory.getSlice(messageOffset, messageSize).map(word => word.toBuffer())); const hashBuffer = sha256(messageData); @@ -168,12 +176,17 @@ export class Pedersen extends Instruction { // We hash a set of field elements const genIndex = Number(memory.get(genIndexOffset).toBigInt()); + memory.checkTag(TypeTag.UINT32, genIndexOffset); const messageSize = Number(memory.get(messageSizeOffset).toBigInt()); + // TODO: Enable when Noir uses UINT32 + // memory.checkTag(TypeTag.UINT32, messageSizeOffset); const hashData = memory.getSlice(messageOffset, messageSize); const memoryOperations = { reads: messageSize + 2, writes: 1, indirect: this.indirect }; context.machineState.consumeGas(this.gasCost(memoryOperations)); + memory.checkTagsRange(TypeTag.FIELD, messageOffset, messageSize); + // No domain sep for now const hash = pedersenHash(hashData, genIndex); memory.set(dstOffset, new Field(hash)); diff --git a/yarn-project/simulator/src/avm/opcodes/instruction_impl.ts b/yarn-project/simulator/src/avm/opcodes/instruction_impl.ts index 18ad99bf749..f5797d526f8 100644 --- a/yarn-project/simulator/src/avm/opcodes/instruction_impl.ts +++ b/yarn-project/simulator/src/avm/opcodes/instruction_impl.ts @@ -1,6 +1,7 @@ import { type AvmContext } from '../avm_context.js'; import { type MemoryValue } from '../avm_memory_types.js'; import { OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; /** Wire format that informs deserialization for instructions with two operands. */ @@ -72,7 +73,9 @@ export abstract class GetterInstruction extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - memory.set(this.dstOffset, this.getValue(context)); + const [dstOffset] = Addressing.fromWire(this.indirect).resolve([this.dstOffset], memory); + + memory.set(dstOffset, this.getValue(context)); memory.assert(memoryOperations); context.machineState.incrementPc(); diff --git a/yarn-project/simulator/src/avm/opcodes/memory.ts b/yarn-project/simulator/src/avm/opcodes/memory.ts index 8edb559aca0..35139cbbcff 100644 --- a/yarn-project/simulator/src/avm/opcodes/memory.ts +++ b/yarn-project/simulator/src/avm/opcodes/memory.ts @@ -114,12 +114,17 @@ export class CMov extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const a = memory.get(this.aOffset); - const b = memory.get(this.bOffset); - const cond = memory.get(this.condOffset); + const [aOffset, bOffset, condOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve( + [this.aOffset, this.bOffset, this.condOffset, this.dstOffset], + memory, + ); + + const a = memory.get(aOffset); + const b = memory.get(bOffset); + const cond = memory.get(condOffset); // TODO: reconsider toBigInt() here - memory.set(this.dstOffset, cond.toBigInt() > 0 ? a : b); + memory.set(dstOffset, cond.toBigInt() > 0 ? a : b); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -130,8 +135,8 @@ export class Cast extends TwoOperandInstruction { static readonly type: string = 'CAST'; static readonly opcode = Opcode.CAST; - constructor(indirect: number, dstTag: number, aOffset: number, dstOffset: number) { - super(indirect, dstTag, aOffset, dstOffset); + constructor(indirect: number, dstTag: number, srcOffset: number, dstOffset: number) { + super(indirect, dstTag, srcOffset, dstOffset); } public async execute(context: AvmContext): Promise { @@ -139,13 +144,14 @@ export class Cast extends TwoOperandInstruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const a = memory.get(this.aOffset); + const [srcOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.dstOffset], memory); + + const a = memory.get(srcOffset); - // TODO: consider not using toBigInt() const casted = this.inTag == TypeTag.FIELD ? new Field(a.toBigInt()) : TaggedMemory.integralFromTag(a.toBigInt(), this.inTag); - memory.set(this.dstOffset, casted); + memory.set(dstOffset, casted); memory.assert(memoryOperations); context.machineState.incrementPc(); @@ -204,10 +210,11 @@ export class CalldataCopy extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const [dstOffset] = Addressing.fromWire(this.indirect).resolve([this.dstOffset], memory); + // We don't need to check tags here because: (1) the calldata is NOT in memory, and (2) we are the ones writing to destination. + const [cdOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.cdOffset, this.dstOffset], memory); const transformedData = context.environment.calldata - .slice(this.cdOffset, this.cdOffset + this.copySize) + .slice(cdOffset, cdOffset + this.copySize) .map(f => new Field(f)); memory.setSlice(dstOffset, transformedData); diff --git a/yarn-project/simulator/src/avm/opcodes/storage.ts b/yarn-project/simulator/src/avm/opcodes/storage.ts index a3b379bac24..c7ed057d0d4 100644 --- a/yarn-project/simulator/src/avm/opcodes/storage.ts +++ b/yarn-project/simulator/src/avm/opcodes/storage.ts @@ -1,7 +1,7 @@ import { Fr } from '@aztec/foundation/fields'; import type { AvmContext } from '../avm_context.js'; -import { Field } from '../avm_memory_types.js'; +import { Field, TypeTag } from '../avm_memory_types.js'; import { StaticCallAlterationError } from '../errors.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Addressing } from './addressing_mode.js'; @@ -45,6 +45,8 @@ export class SStore extends BaseStorageInstruction { context.machineState.consumeGas(this.gasCost(memoryOperations)); const [srcOffset, slotOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.bOffset], memory); + memory.checkTag(TypeTag.FIELD, slotOffset); + memory.checkTagsRange(TypeTag.FIELD, srcOffset, this.size); const slot = memory.get(slotOffset).toFr(); const data = memory.getSlice(srcOffset, this.size).map(field => field.toFr()); @@ -72,21 +74,19 @@ export class SLoad extends BaseStorageInstruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - const [aOffset, size, bOffset] = Addressing.fromWire(this.indirect).resolve( - [this.aOffset, this.size, this.bOffset], - memory, - ); + const [slotOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.aOffset, this.bOffset], memory); + memory.checkTag(TypeTag.FIELD, slotOffset); - const slot = memory.get(aOffset); + const slot = memory.get(slotOffset); // Write each read value from storage into memory - for (let i = 0; i < size; i++) { + for (let i = 0; i < this.size; i++) { const data: Fr = await context.persistableState.readStorage( context.environment.storageAddress, new Fr(slot.toBigInt() + BigInt(i)), ); - memory.set(bOffset + i, new Field(data)); + memory.set(dstOffset + i, new Field(data)); } context.machineState.incrementPc();