diff --git a/avm-transpiler/src/instructions.rs b/avm-transpiler/src/instructions.rs index f2ab5288100d..9df6f20551c6 100644 --- a/avm-transpiler/src/instructions.rs +++ b/avm-transpiler/src/instructions.rs @@ -7,7 +7,7 @@ use crate::opcodes::AvmOpcode; pub const ALL_DIRECT: u8 = 0b00000000; pub const ZEROTH_OPERAND_INDIRECT: u8 = 0b00000001; pub const FIRST_OPERAND_INDIRECT: u8 = 0b00000010; -pub const ZEROTH_FIRST_OPERANDS_INDIRECT: u8 = 0b00000011; +pub const ZEROTH_FIRST_OPERANDS_INDIRECT: u8 = ZEROTH_OPERAND_INDIRECT | FIRST_OPERAND_INDIRECT; /// A simple representation of an AVM instruction for the purpose /// of generating an AVM bytecode from Brillig. diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index cac1442692f7..e4a09137776f 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -238,6 +238,9 @@ fn handle_foreign_call( inputs: &Vec, ) { match function { + "amvOpcodeEmitUnencryptedLog" => { + handle_emit_unencrypted_log(avm_instrs, destinations, inputs) + }, "avmOpcodeNoteHashExists" => handle_note_hash_exists(avm_instrs, destinations, inputs), "avmOpcodeEmitNoteHash" | "avmOpcodeEmitNullifier" => handle_emit_note_hash_or_nullifier( function == "avmOpcodeEmitNullifier", @@ -306,6 +309,43 @@ fn handle_note_hash_exists( }); } +fn handle_emit_unencrypted_log( + avm_instrs: &mut Vec, + destinations: &Vec, + inputs: &Vec, +) { + if destinations.len() != 0 || inputs.len() != 2 { + panic!( + "Transpiler expects ForeignCall::EMITUNENCRYPTEDLOG to have 0 destinations and 3 inputs, got {} and {}", + destinations.len(), + inputs.len() + ); + } + let (event_offset, message_array) = match &inputs[..] { + [ValueOrArray::MemoryAddress(offset), ValueOrArray::HeapArray(array)] => { + (offset.to_usize() as u32, array) + } + _ => panic!("Unexpected inputs for ForeignCall::EMITUNENCRYPTEDLOG: {:?}", inputs), + }; + avm_instrs.push(AvmInstruction { + opcode: AvmOpcode::EMITUNENCRYPTEDLOG, + // The message array from Brillig is indirect. + indirect: Some(FIRST_OPERAND_INDIRECT), + operands: vec![ + AvmOperand::U32 { + value: event_offset, + }, + AvmOperand::U32 { + value: message_array.pointer.to_usize() as u32, + }, + AvmOperand::U32 { + value: message_array.size as u32, + }, + ], + ..Default::default() + }); +} + /// Handle an AVM EMITNOTEHASH or EMITNULLIFIER instruction /// (an emitNoteHash or emitNullifier brillig foreign call was encountered) /// Adds the new instruction to the avm instructions list. diff --git a/noir-projects/aztec-nr/aztec/src/context/avm.nr b/noir-projects/aztec-nr/aztec/src/context/avm.nr index e2fc45193972..0d2c98e82e26 100644 --- a/noir-projects/aztec-nr/aztec/src/context/avm.nr +++ b/noir-projects/aztec-nr/aztec/src/context/avm.nr @@ -1,4 +1,5 @@ use dep::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH}; +use dep::protocol_types::traits::{Serialize}; // Getters that will be converted by the transpiler into their // own opcodes @@ -62,6 +63,17 @@ impl AVMContext { #[oracle(avmOpcodeEmitNullifier)] pub fn emit_nullifier(self, nullifier: Field) {} + /** + * Emit a log with the given event selector and message. + * + * @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. + */ + #[oracle(amvOpcodeEmitUnencryptedLog)] + pub fn emit_unencrypted_log(self, event_selector: Field, message: T) {} + #[oracle(avmOpcodeL1ToL2MsgExists)] pub fn l1_to_l2_msg_exists(self, msg_hash: Field, msg_leaf_index: Field) -> u8 {} diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml index f3d8583d4335..bb61ffae56bb 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml @@ -6,3 +6,4 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } +compressed_string = { path = "../../../aztec-nr/compressed-string" } \ 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 8ea44b2ca78f..09f6a6b23de8 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 @@ -1,6 +1,7 @@ contract AvmTest { // Libs use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH}; + use dep::compressed_string::CompressedString; // avm lib use dep::aztec::avm::hash::{keccak256, poseidon, sha256}; @@ -140,6 +141,16 @@ contract AvmTest { // context.contract_call_depth() // } + #[aztec(public-vm)] + fn emit_unencrypted_log() { + context.emit_unencrypted_log(/*event_selector=*/ 5, /*message=*/ [10, 20, 30]); + context.emit_unencrypted_log(/*event_selector=*/ 8, /*message=*/ "Hello, world!"); + // FIXME: Try this once Brillig codegen produces uniform bit sizes for LT + // FIXME: TagCheckError: Tag mismatch at offset 22, got UINT64, expected UINT32 + // let s: CompressedString<1,13> = CompressedString::from_string("Hello, world!"); + // context.emit_unencrypted_log(/*event_selector=*/ 10, /*message=*/ s); + } + #[aztec(public-vm)] fn note_hash_exists(note_hash: Field, leaf_index: Field) -> pub u8 { context.note_hash_exists(note_hash, leaf_index) diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 382af6d4be1e..ea5a7ed378eb 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -1,3 +1,5 @@ +import { UnencryptedL2Log } from '@aztec/circuit-types'; +import { EventSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { keccak, pedersenHash, poseidonHash, sha256 } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -298,6 +300,44 @@ describe('AVM simulator', () => { const trace = context.persistableState.flush(); expect(trace.noteHashChecks).toEqual([expect.objectContaining({ noteHash, leafIndex, exists: true })]); }); + it(`Should execute contract function to emit unencrypted logs (should be traced)`, async () => { + // Get contract function artifact + const artifact = AvmTestContractArtifact.functions.find(f => f.name === 'avm_emit_unencrypted_log')!; + + // Decode bytecode into instructions + const bytecode = Buffer.from(artifact.bytecode, 'base64'); + + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(bytecode)); + + const results = await new AvmSimulator(context).execute(); + + expect(results.reverted).toBe(false); + + const expectedFields = [new Fr(10), new Fr(20), new Fr(30)]; + const expectedString = 'Hello, world!'.split('').map(c => new Fr(c.charCodeAt(0))); + // FIXME: Try this once Brillig codegen produces uniform bit sizes for LT + // const expectedCompressedString = Buffer.from('Hello, world!'); + expect(context.persistableState.flush().newLogs).toEqual([ + new UnencryptedL2Log( + context.environment.address, + new EventSelector(5), + Buffer.concat(expectedFields.map(f => f.toBuffer())), + ), + new UnencryptedL2Log( + context.environment.address, + new EventSelector(8), + Buffer.concat(expectedString.map(f => f.toBuffer())), + ), + // new UnencryptedL2Log( + // context.environment.address, + // new EventSelector(10), + // expectedCompressedString, + // ), + ]); + }); it(`Should execute contract function to emit note hash (should be traced)`, async () => { const utxo = new Fr(42); const calldata = [utxo]; diff --git a/yarn-project/simulator/src/avm/avm_simulator.ts b/yarn-project/simulator/src/avm/avm_simulator.ts index a4b0d29b6911..4289e5deeda4 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.ts @@ -35,7 +35,7 @@ export class AvmSimulator { const instruction = instructions[this.context.machineState.pc]; assert(!!instruction); // This should never happen - this.log(`Executing PC=${this.context.machineState.pc}: ${instruction.toString()}`); + this.log.debug(`@${this.context.machineState.pc} ${instruction.toString()}`); // Execute the instruction. // Normal returns and reverts will return normally here. // "Exceptional halts" will throw. diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index bcee9bbea5a3..83be8cbf5609 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -1,4 +1,6 @@ -import { EthAddress } from '@aztec/circuits.js'; +import { UnencryptedL2Log } from '@aztec/circuit-types'; +import { AztecAddress, EthAddress } from '@aztec/circuits.js'; +import { EventSelector } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -150,15 +152,15 @@ describe('journal', () => { const recipient = EthAddress.fromField(new Fr(42)); const commitment = new Fr(10); const commitmentT1 = new Fr(20); - const logs = [new Fr(1), new Fr(2)]; - const logsT1 = [new Fr(3), new Fr(4)]; + const log = { address: 10n, selector: 5, data: [new Fr(5), new Fr(6)] }; + const logT1 = { address: 20n, selector: 8, data: [new Fr(7), new Fr(8)] }; const index = new Fr(42); const indexT1 = new Fr(24); journal.writeStorage(contractAddress, key, value); await journal.readStorage(contractAddress, key); journal.writeNoteHash(commitment); - journal.writeLog(logs); + journal.writeLog(new Fr(log.address), new Fr(log.selector), log.data); journal.writeL1Message(recipient, commitment); await journal.writeNullifier(contractAddress, commitment); await journal.checkNullifierExists(contractAddress, commitment); @@ -168,7 +170,7 @@ describe('journal', () => { childJournal.writeStorage(contractAddress, key, valueT1); await childJournal.readStorage(contractAddress, key); childJournal.writeNoteHash(commitmentT1); - childJournal.writeLog(logsT1); + childJournal.writeLog(new Fr(logT1.address), new Fr(logT1.selector), logT1.data); childJournal.writeL1Message(recipient, commitmentT1); await childJournal.writeNullifier(contractAddress, commitmentT1); await childJournal.checkNullifierExists(contractAddress, commitmentT1); @@ -195,7 +197,18 @@ describe('journal', () => { expect(slotWrites).toEqual([value, valueT1]); expect(journalUpdates.newNoteHashes).toEqual([commitment, commitmentT1]); - expect(journalUpdates.newLogs).toEqual([logs, logsT1]); + expect(journalUpdates.newLogs).toEqual([ + new UnencryptedL2Log( + AztecAddress.fromBigInt(log.address), + new EventSelector(log.selector), + Buffer.concat(log.data.map(f => f.toBuffer())), + ), + new UnencryptedL2Log( + AztecAddress.fromBigInt(logT1.address), + new EventSelector(logT1.selector), + Buffer.concat(logT1.data.map(f => f.toBuffer())), + ), + ]); expect(journalUpdates.newL1Messages).toEqual([ { recipient, content: commitment }, { recipient, content: commitmentT1 }, @@ -228,8 +241,8 @@ describe('journal', () => { const recipient = EthAddress.fromField(new Fr(42)); const commitment = new Fr(10); const commitmentT1 = new Fr(20); - const logs = [new Fr(1), new Fr(2)]; - const logsT1 = [new Fr(3), new Fr(4)]; + const log = { address: 10n, selector: 5, data: [new Fr(5), new Fr(6)] }; + const logT1 = { address: 20n, selector: 8, data: [new Fr(7), new Fr(8)] }; const index = new Fr(42); const indexT1 = new Fr(24); @@ -239,7 +252,7 @@ describe('journal', () => { await journal.writeNullifier(contractAddress, commitment); await journal.checkNullifierExists(contractAddress, commitment); await journal.checkL1ToL2MessageExists(commitment, index); - journal.writeLog(logs); + journal.writeLog(new Fr(log.address), new Fr(log.selector), log.data); journal.writeL1Message(recipient, commitment); const childJournal = new AvmPersistableStateManager(journal.hostStorage, journal); @@ -249,7 +262,7 @@ describe('journal', () => { await childJournal.writeNullifier(contractAddress, commitmentT1); await childJournal.checkNullifierExists(contractAddress, commitmentT1); await journal.checkL1ToL2MessageExists(commitmentT1, indexT1); - childJournal.writeLog(logsT1); + childJournal.writeLog(new Fr(logT1.address), new Fr(logT1.selector), logT1.data); childJournal.writeL1Message(recipient, commitmentT1); journal.rejectNestedCallState(childJournal); @@ -285,7 +298,13 @@ describe('journal', () => { ]); // Check that rejected Accrued Substate is absent - expect(journalUpdates.newLogs).toEqual([logs]); + expect(journalUpdates.newLogs).toEqual([ + new UnencryptedL2Log( + AztecAddress.fromBigInt(log.address), + new EventSelector(log.selector), + Buffer.concat(log.data.map(f => f.toBuffer())), + ), + ]); expect(journalUpdates.newL1Messages).toEqual([{ recipient, content: commitment }]); }); diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 936a3a127f46..be16424c07af 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -1,4 +1,6 @@ -import { EthAddress, L2ToL1Message } from '@aztec/circuits.js'; +import { UnencryptedL2Log } from '@aztec/circuit-types'; +import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js'; +import { EventSelector } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; import { HostStorage } from './host_storage.js'; @@ -18,7 +20,7 @@ export type JournalData = { l1ToL2MessageChecks: TracedL1toL2MessageCheck[]; newL1Messages: L2ToL1Message[]; - newLogs: Fr[][]; + newLogs: UnencryptedL2Log[]; /** contract address -\> key -\> value */ currentStorageValue: Map>; @@ -53,7 +55,7 @@ export class AvmPersistableStateManager { /** Accrued Substate **/ private newL1Messages: L2ToL1Message[] = []; - private newLogs: Fr[][] = []; + private newLogs: UnencryptedL2Log[] = []; constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) { this.hostStorage = hostStorage; @@ -174,8 +176,14 @@ export class AvmPersistableStateManager { this.newL1Messages.push(new L2ToL1Message(recipientAddress, content)); } - public writeLog(log: Fr[]) { - this.newLogs.push(log); + public writeLog(contractAddress: Fr, event: Fr, log: Fr[]) { + this.newLogs.push( + new UnencryptedL2Log( + AztecAddress.fromField(contractAddress), + EventSelector.fromField(event), + Buffer.concat(log.map(f => f.toBuffer())), + ), + ); } /** 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 1dbe2f5ad5b8..41470dc95e57 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts @@ -1,4 +1,6 @@ +import { UnencryptedL2Log } from '@aztec/circuit-types'; import { EthAddress, Fr } from '@aztec/circuits.js'; +import { EventSelector } from '@aztec/foundation/abi'; import { mock } from 'jest-mock-extended'; @@ -354,10 +356,16 @@ describe('Accrued Substate', () => { const buf = Buffer.from([ EmitUnencryptedLog.opcode, // opcode 0x01, // indirect + ...Buffer.from('02345678', 'hex'), // event selector offset ...Buffer.from('12345678', 'hex'), // offset ...Buffer.from('a2345678', 'hex'), // length ]); - const inst = new EmitUnencryptedLog(/*indirect=*/ 0x01, /*offset=*/ 0x12345678, /*length=*/ 0xa2345678); + const inst = new EmitUnencryptedLog( + /*indirect=*/ 0x01, + /*eventSelectorOffset=*/ 0x02345678, + /*offset=*/ 0x12345678, + /*length=*/ 0xa2345678, + ); expect(EmitUnencryptedLog.deserialize(buf)).toEqual(inst); expect(inst.serialize()).toEqual(buf); @@ -365,17 +373,25 @@ describe('Accrued Substate', () => { it('Should append unencrypted logs correctly', async () => { const startOffset = 0; + const eventSelector = 5; + const eventSelectorOffset = 10; const values = [new Field(69n), new Field(420n), new Field(Field.MODULUS - 1n)]; - context.machineState.memory.setSlice(0, values); - - const length = values.length; + context.machineState.memory.setSlice(startOffset, values); + context.machineState.memory.set(eventSelectorOffset, new Field(eventSelector)); - await new EmitUnencryptedLog(/*indirect=*/ 0, /*offset=*/ startOffset, length).execute(context); + await new EmitUnencryptedLog( + /*indirect=*/ 0, + eventSelectorOffset, + /*offset=*/ startOffset, + values.length, + ).execute(context); const journalState = context.persistableState.flush(); - const expected = values.map(v => v.toFr()); - expect(journalState.newLogs).toEqual([expected]); + const expectedLog = Buffer.concat(values.map(v => v.toFr().toBuffer())); + expect(journalState.newLogs).toEqual([ + new UnencryptedL2Log(context.environment.address, new EventSelector(eventSelector), expectedLog), + ]); }); }); @@ -423,7 +439,7 @@ describe('Accrued Substate', () => { const instructions = [ new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ 0), new EmitNullifier(/*indirect=*/ 0, /*offset=*/ 0), - new EmitUnencryptedLog(/*indirect=*/ 0, /*offset=*/ 0, 1), + new EmitUnencryptedLog(/*indirect=*/ 0, /*eventSelector=*/ 0, /*offset=*/ 0, /*logSize=*/ 1), 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 d34354336dc5..15535182288e 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.ts @@ -3,6 +3,7 @@ import { Uint8 } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; import { NullifierCollisionError } from '../journal/nullifiers.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; import { StaticCallStorageAlterError } from './storage.js'; @@ -153,9 +154,20 @@ export class EmitUnencryptedLog extends Instruction { static type: string = 'EMITUNENCRYPTEDLOG'; static readonly opcode: Opcode = Opcode.EMITUNENCRYPTEDLOG; // Informs (de)serialization. See Instruction.deserialize. - static readonly wireFormat = [OperandType.UINT8, OperandType.UINT8, OperandType.UINT32, OperandType.UINT32]; + static readonly wireFormat = [ + OperandType.UINT8, + OperandType.UINT8, + OperandType.UINT32, + OperandType.UINT32, + OperandType.UINT32, + ]; - constructor(private indirect: number, private logOffset: number, private logSize: number) { + constructor( + private indirect: number, + private eventSelectorOffset: number, + private logOffset: number, + private logSize: number, + ) { super(); } @@ -164,8 +176,15 @@ export class EmitUnencryptedLog extends Instruction { throw new StaticCallStorageAlterError(); } - const log = context.machineState.memory.getSlice(this.logOffset, this.logSize).map(f => f.toFr()); - context.persistableState.writeLog(log); + const [eventSelectorOffset, logOffset] = Addressing.fromWire(this.indirect).resolve( + [this.eventSelectorOffset, this.logOffset], + context.machineState.memory, + ); + + const contractAddress = context.environment.address; + const event = context.machineState.memory.get(eventSelectorOffset).toFr(); + const log = context.machineState.memory.getSlice(logOffset, this.logSize).map(f => f.toFr()); + context.persistableState.writeLog(contractAddress, event, log); context.machineState.incrementPc(); } diff --git a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx index a3f3f5770495..0e6a9bfc235b 100644 --- a/yellow-paper/docs/public-vm/gen/_instruction-set.mdx +++ b/yellow-paper/docs/public-vm/gen/_instruction-set.mdx @@ -391,6 +391,7 @@ if exists: {`context.accruedSubstate.unencryptedLogs.append( UnencryptedLog { address: context.environment.address, + eventSelector: M[eventSelectorOffset], log: M[logOffset:logOffset+logSize], } )`} @@ -1566,6 +1567,7 @@ Emit an unencrypted log - **Flags**: - **indirect**: Toggles whether each memory-offset argument is an indirect offset. Rightmost bit corresponds to 0th offset arg, etc. Indirect offsets result in memory accesses like `M[M[offset]]` instead of the more standard `M[offset]`. - **Args**: + - **eventSelectorOffset**: memory offset of the event selector - **logOffset**: memory offset of the data to log - **logSize**: number of words to log - **Expression**: @@ -1573,11 +1575,12 @@ Emit an unencrypted log {`context.accruedSubstate.unencryptedLogs.append( UnencryptedLog { address: context.environment.address, + eventSelector: M[eventSelectorOffset], log: M[logOffset:logOffset+logSize], } )`} -- **Bit-size**: 88 +- **Bit-size**: 120 [![](./images/bit-formats/EMITUNENCRYPTEDLOG.png)](./images/bit-formats/EMITUNENCRYPTEDLOG.png) diff --git a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js index ebfd3d0ffb30..b4163df2e8f4 100644 --- a/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js +++ b/yellow-paper/src/preprocess/InstructionSet/InstructionSet.js @@ -1028,6 +1028,7 @@ T[dstOffset] = field {"name": "indirect", "description": INDIRECT_FLAG_DESCRIPTION}, ], "Args": [ + {"name": "eventSelectorOffset", "description": "memory offset of the event selector"}, {"name": "logOffset", "description": "memory offset of the data to log"}, {"name": "logSize", "description": "number of words to log", "mode": "immediate", "type": "u32"}, ], @@ -1035,6 +1036,7 @@ T[dstOffset] = field context.accruedSubstate.unencryptedLogs.append( UnencryptedLog { address: context.environment.address, + eventSelector: M[eventSelectorOffset], log: M[logOffset:logOffset+logSize], } )