import { CONTRACT_CLASS_LOG_LENGTH, CONTRACT_CLASS_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import { schemas } from '@aztec/foundation/schemas'; import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import type { FieldsOf } from '@aztec/foundation/types'; import { inspect } from 'util'; import { z } from 'zod'; import { AztecAddress } from '../aztec-address/index.js'; export class ContractClassLogFields { // Below line gives error 'Type instantiation is excessively deep and possibly infinite. ts(2589)' // public fields: Tuple constructor(public fields: Fr[]) { if (fields.length !== CONTRACT_CLASS_LOG_SIZE_IN_FIELDS) { throw new Error( `Invalid number of fields for ContractClassLog. Expected ${CONTRACT_CLASS_LOG_SIZE_IN_FIELDS}, got ${fields.length}`, ); } } static get schema() { return z .object({ fields: z.array(schemas.Fr).refine(arr => arr.length === CONTRACT_CLASS_LOG_SIZE_IN_FIELDS), }) .transform(({ fields }) => new ContractClassLogFields(fields)); } /** * Creates a ContractClassLogFields from a plain object without Zod validation. * This method is optimized for performance and skips validation, making it suitable * for deserializing trusted data (e.g., from C++ via MessagePack). * @param obj - Plain object containing ContractClassLogFields fields * @returns A ContractClassLogFields instance */ static fromPlainObject(obj: any): ContractClassLogFields { if (obj instanceof ContractClassLogFields) { return obj; } return new ContractClassLogFields(obj.fields.map((f: any) => Fr.fromPlainObject(f))); } toFields(): Fr[] { return this.fields; } static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); // Below line gives error 'Type instantiation is excessively deep and possibly infinite. ts(2589)' // reader.readFieldArray(CONTRACT_CLASS_LOG_LENGTH); return new ContractClassLogFields( Array.from({ length: CONTRACT_CLASS_LOG_SIZE_IN_FIELDS }, () => reader.readField()), ); } toBuffer() { return serializeToBuffer(this.fields); } static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); // Below line gives error 'Type instantiation is excessively deep and possibly infinite. ts(2589)' // reader.readArray(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, Fr) return new ContractClassLogFields( Array.from({ length: CONTRACT_CLASS_LOG_SIZE_IN_FIELDS }, () => reader.readObject(Fr)), ); } equals(other: ContractClassLogFields) { return this.fields.every((f, i) => f.equals(other.fields[i])); } getEmittedFields(emittedLength: number) { return this.fields.slice(0, emittedLength); } static fromEmittedFields(emittedFields: Fr[]) { return new ContractClassLogFields( emittedFields.concat(Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - emittedFields.length).fill(Fr.ZERO)), ); } isEmpty() { return this.fields.every(f => f.isZero()); } static empty() { return new ContractClassLogFields(Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS).fill(Fr.ZERO)); } static random(emittedLength = CONTRACT_CLASS_LOG_SIZE_IN_FIELDS) { return new ContractClassLogFields( Array.from({ length: emittedLength }, () => Fr.random()).concat( Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - emittedLength).fill(Fr.ZERO), ), ); } async hash() { return await poseidon2Hash(this.fields); } clone() { return ContractClassLogFields.fromBuffer(this.toBuffer()); } } export class ContractClassLog { static SIZE_IN_BYTES = Fr.SIZE_IN_BYTES * CONTRACT_CLASS_LOG_LENGTH; constructor( public contractAddress: AztecAddress, public fields: ContractClassLogFields, public emittedLength: number, ) {} static from(fields: FieldsOf) { return new ContractClassLog(fields.contractAddress, fields.fields, fields.emittedLength); } toFields(): Fr[] { return serializeToFields([this.contractAddress, this.fields, this.emittedLength]); } equals(other: ContractClassLog) { return ( this.contractAddress.equals(other.contractAddress) && this.fields.equals(other.fields) && this.emittedLength === other.emittedLength ); } static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); return new ContractClassLog( reader.readObject(AztecAddress), reader.readObject(ContractClassLogFields), reader.readU32(), ); } getEmittedFields() { return this.fields.getEmittedFields(this.emittedLength); } toBlobFields(): Fr[] { return [this.contractAddress.toField()].concat(this.getEmittedFields()); } static fromBlobFields(emittedLength: number, fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); const contractAddress = reader.readObject(AztecAddress); const emittedFields = reader.readFieldArray(emittedLength); return new ContractClassLog( contractAddress, ContractClassLogFields.fromEmittedFields(emittedFields), emittedLength, ); } isEmpty() { return this.contractAddress.isZero() && this.fields.isEmpty() && this.emittedLength === 0; } static empty() { return new ContractClassLog(AztecAddress.ZERO, ContractClassLogFields.empty(), 0); } toBuffer(): Buffer { return serializeToBuffer([this.contractAddress, this.fields, this.emittedLength]); } static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); const address = reader.readObject(AztecAddress); const fields = ContractClassLogFields.fromBuffer(reader); const emittedLength = reader.readNumber(); return new ContractClassLog(address, fields, emittedLength); } static async random() { // NB: Using half the maximum number of fields per log because max fields keeps overfilling blobs in tests. const emittedLength = Math.ceil(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS / 2); return new ContractClassLog( await AztecAddress.random(), ContractClassLogFields.random(emittedLength), emittedLength, ); } async hash() { return await this.fields.hash(); } static get schema() { return z .object({ contractAddress: AztecAddress.schema, fields: ContractClassLogFields.schema, emittedLength: z.number(), }) .transform(ContractClassLog.from); } /** * Creates a ContractClassLog from a plain object without Zod validation. * This method is optimized for performance and skips validation, making it suitable * for deserializing trusted data (e.g., from C++ via MessagePack). * @param obj - Plain object containing ContractClassLog fields * @returns A ContractClassLog instance */ static fromPlainObject(obj: any): ContractClassLog { if (obj instanceof ContractClassLog) { return obj; } return new ContractClassLog( AztecAddress.fromPlainObject(obj.contractAddress), ContractClassLogFields.fromPlainObject(obj.fields), obj.emittedLength, ); } [inspect.custom](): string { return `ContractClassLog { contractAddress: ${this.contractAddress.toString()}, emittedLength: ${this.emittedLength}, fields: [${this.fields.fields.map((x: Fr) => inspect(x)).join(', ')}], }`; } }