From d953090ca9eba0184d10c0b8ddbc60998bc155f0 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 5 Feb 2024 10:17:45 -0300 Subject: [PATCH] feat: Contract class registerer contract (#4403) Adds contract class registerer canonical contract, and listens to its events from the archiver. This contract is meant for broadcasting the public bytecode of any given contract class. If the public bytecode for a contract class has not been broadcasted, the public kernel should refuse to execute any calls to a contract instance of this class. More info [here](https://yp-aztec.netlify.app/docs/contract-deployment/classes#canonical-contract-class-registerer). For now the contract assigned to a hardcoded address in the archiver, computed with salt=1. And since event selectors are not yet implemented (all events are emitted with selector `5`) uses a magic number for identifying the event, which should be removed later. Other changes include: - Add serialization for an arbitrary-sized buffer into a Field array, where each field carries 31 bytes of info of the original buffer. - Increase the maximum number of args that can be passed (from 512 to 1024) to a Noir private function. - Introduce a new `ContractClassPublic` interface that represents a contract class that has info on public functions only, and uses it in the Aztec Node. - Remove the `SerializableContractClass` class in favor of serialization functions used only in the archiver. - Add an endpoint to the Aztec Node for retrieving contract classes. Pending for future PRs: - Unhardcode the registerer contract address from the archiver. - Set up a nicer API for class registration. - Generalize buffer serialization into field array. - Add methods for broadcasting private and unconstrained functions. Fixes #4069 Fixes #4070 --- .../src/core/libraries/ConstantsGen.sol | 5 +- .../src/client/client_execution_context.ts | 3 +- .../archiver/src/archiver/archiver.ts | 62 ++++++- .../archiver/src/archiver/archiver_store.ts | 6 +- .../src/archiver/archiver_store_test_suite.ts | 12 +- .../kv_archiver_store/contract_class_store.ts | 56 +++++-- .../kv_archiver_store/kv_archiver_store.ts | 6 +- .../memory_archiver_store.ts | 8 +- .../aztec-node/src/aztec-node/server.ts | 5 + .../circuit-types/src/contract_data.ts | 7 + .../src/interfaces/aztec-node.ts | 7 + .../src/logs/l2_block_l2_logs.ts | 8 +- .../src/logs/unencrypted_l2_log.ts | 8 +- .../ContractClassRegisteredEventData.hex | 1 + .../src/abis/__snapshots__/abis.test.ts.snap | 124 +++++++------- .../circuits.js/src/abis/abis.test.ts | 1 - yarn-project/circuits.js/src/abis/abis.ts | 21 +-- yarn-project/circuits.js/src/constants.gen.ts | 4 +- .../contract_address.test.ts.snap | 62 +++---- .../__snapshots__/contract_class.test.ts.snap | 4 +- .../src/contract/contract_class.ts | 22 +-- .../src/contract/contract_class_id.ts | 17 +- .../contract_class_registered_event.test.ts | 18 +++ .../contract_class_registered_event.ts | 75 +++++++++ .../circuits.js/src/contract/index.ts | 2 + .../src/contract/public_bytecode.test.ts | 31 ++++ .../src/contract/public_bytecode.ts | 74 +++++++++ .../circuits.js/src/scripts/constants.in.ts | 14 +- .../circuits.js/src/tests/factories.ts | 38 +++++ .../circuits.js/src/tests/fixtures.ts | 13 +- .../end-to-end/src/cli_docs_sandbox.test.ts | 1 + .../src/e2e_deploy_contract.test.ts | 62 ++++++- yarn-project/foundation/src/fields/fields.ts | 12 +- .../foundation/src/serialize/buffer_reader.ts | 7 + yarn-project/foundation/src/types/index.ts | 7 +- yarn-project/noir-contracts/Nargo.toml | 1 + .../Nargo.toml | 8 + .../src/main.nr | 67 ++++++++ .../src/__snapshots__/index.test.ts.snap | 43 +++++ .../src/crates/types/src/constants.nr | 11 +- .../src/crates/types/src/interop_testing.nr | 12 +- .../noir-protocol-circuits/src/index.test.ts | 9 +- .../src/contracts/contract_class.test.ts | 8 - .../types/src/contracts/contract_class.ts | 153 +++--------------- 44 files changed, 795 insertions(+), 320 deletions(-) create mode 100644 yarn-project/circuits.js/fixtures/ContractClassRegisteredEventData.hex create mode 100644 yarn-project/circuits.js/src/contract/contract_class_registered_event.test.ts create mode 100644 yarn-project/circuits.js/src/contract/contract_class_registered_event.ts create mode 100644 yarn-project/circuits.js/src/contract/public_bytecode.test.ts create mode 100644 yarn-project/circuits.js/src/contract/public_bytecode.ts create mode 100644 yarn-project/noir-contracts/contracts/contract_class_registerer_contract/Nargo.toml create mode 100644 yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr delete mode 100644 yarn-project/types/src/contracts/contract_class.test.ts diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 68465421abd..6ba0c014d94 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -62,7 +62,10 @@ library Constants { uint256 internal constant MAPPING_SLOT_PEDERSEN_SEPARATOR = 4; uint256 internal constant NUM_FIELDS_PER_SHA256 = 2; uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 32; - uint256 internal constant ARGS_HASH_CHUNK_COUNT = 16; + uint256 internal constant ARGS_HASH_CHUNK_COUNT = 32; + uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000; + uint256 internal constant CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = + 0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8; uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 8; uint256 internal constant L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25; uint256 internal constant MAX_NOTE_FIELDS_LENGTH = 20; diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index 27afde14d26..24e6ca33977 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -291,7 +291,8 @@ export class ClientExecutionContext extends ViewDataOracle { */ public emitUnencryptedLog(log: UnencryptedL2Log) { this.unencryptedLogs.push(log); - this.log(`Emitted unencrypted log: "${log.toHumanReadable()}"`); + const text = log.toHumanReadable(); + this.log(`Emitted unencrypted log: "${text.length > 100 ? text.slice(0, 100) + '...' : text}"`); } /** diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 4856a608bdf..206a333645e 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -14,10 +14,17 @@ import { LogFilter, LogType, TxHash, + UnencryptedL2Log, } from '@aztec/circuit-types'; -import { FunctionSelector, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js'; +import { + CONTRACT_CLASS_REGISTERED_MAGIC_VALUE, + ContractClassRegisteredEvent, + FunctionSelector, + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, +} from '@aztec/circuits.js'; import { createEthereumChain } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; import { padArrayEnd } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -25,7 +32,7 @@ import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; import { ContractClass, - ContractClassWithId, + ContractClassPublic, ContractInstance, ContractInstanceWithAddress, } from '@aztec/types/contracts'; @@ -63,6 +70,12 @@ export class Archiver implements ArchiveSource { */ private lastLoggedL1BlockNumber = 0n; + // TODO(@spalladino): Calculate this on the fly somewhere else! + /** Address of the ClassRegisterer contract with a salt=1 */ + private classRegistererAddress = AztecAddress.fromString( + '0x1c9f737a5ab5a7bb5ea970ba40737d44dc22fbcbe19fd8171429f2c2c433afb5', + ); + /** * Creates a new instance of the Archiver. * @param publicClient - A client for interacting with the Ethereum node. @@ -272,6 +285,16 @@ export class Archiver implements ArchiveSource { ), ); + // Unroll all logs emitted during the retrieved blocks and extract any contract classes from them + await Promise.all( + retrievedBlocks.retrievedData.map(async block => { + const blockLogs = (block.newUnencryptedLogs?.txLogs ?? []) + .flatMap(txLog => txLog.unrollLogs()) + .map(log => UnencryptedL2Log.fromBuffer(log)); + await this.storeRegisteredContractClasses(blockLogs, block.number); + }), + ); + // store contracts for which we have retrieved L2 blocks const lastKnownL2BlockNum = retrievedBlocks.retrievedData[retrievedBlocks.retrievedData.length - 1].number; await Promise.all( @@ -302,6 +325,33 @@ export class Archiver implements ArchiveSource { ); } + /** + * Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract. + * @param allLogs - All logs emitted in a bunch of blocks. + */ + private async storeRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number) { + const contractClasses: ContractClassPublic[] = []; + for (const log of allLogs) { + try { + if ( + !log.contractAddress.equals(this.classRegistererAddress) || + toBigIntBE(log.data.subarray(0, 32)) !== CONTRACT_CLASS_REGISTERED_MAGIC_VALUE + ) { + continue; + } + const event = ContractClassRegisteredEvent.fromLogData(log.data); + contractClasses.push(event.toContractClassPublic()); + } catch (err) { + this.log.warn(`Error processing log ${log.toHumanReadable()}: ${err}`); + } + } + + if (contractClasses.length > 0) { + contractClasses.forEach(c => this.log(`Registering contract class ${c.id.toString()}`)); + await this.store.addContractClasses(contractClasses, blockNum); + } + } + /** * Stores extended contract data as classes and instances. * Temporary solution until we source this data from the contract class registerer and instance deployer. @@ -448,6 +498,10 @@ export class Archiver implements ArchiveSource { return this.store.getBlockNumber(); } + public getContractClass(id: Fr): Promise { + return this.store.getContractClass(id); + } + /** * Gets up to `limit` amount of pending L1 to L2 messages. * @param limit - The number of messages to return. @@ -475,7 +529,7 @@ export class Archiver implements ArchiveSource { */ function extendedContractDataToContractClassAndInstance( data: ExtendedContractData, -): [ContractClassWithId, ContractInstanceWithAddress] { +): [ContractClassPublic, ContractInstanceWithAddress] { const contractClass: ContractClass = { version: 1, artifactHash: Fr.ZERO, @@ -498,7 +552,7 @@ function extendedContractDataToContractClassAndInstance( }; const address = data.contractData.contractAddress; return [ - { ...contractClass, id: contractClassId }, + { ...contractClass, id: contractClassId, privateFunctionsRoot: Fr.ZERO }, { ...contractInstance, address }, ]; } diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index c8d033d40fd..06bc1ed5301 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -12,7 +12,7 @@ import { } from '@aztec/circuit-types'; import { Fr } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; /** * Represents the latest L1 block processed by the archiver for various objects in L2. @@ -175,13 +175,13 @@ export interface ArchiverDataStore { * @param blockNumber - Number of the L2 block the contracts were registered in. * @returns True if the operation is successful. */ - addContractClasses(data: ContractClassWithId[], blockNumber: number): Promise; + addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise; /** * Returns a contract class given its id, or undefined if not exists. * @param id - Id of the contract class. */ - getContractClass(id: Fr): Promise; + getContractClass(id: Fr): Promise; /** * Add new contract instances from an L2 block to the store's list. diff --git a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts index 592addaefb0..b590b78a762 100644 --- a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts +++ b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts @@ -11,13 +11,9 @@ import { } from '@aztec/circuit-types'; import '@aztec/circuit-types/jest'; import { AztecAddress, Fr } from '@aztec/circuits.js'; +import { makeContractClassPublic } from '@aztec/circuits.js/factories'; import { randomBytes } from '@aztec/foundation/crypto'; -import { - ContractClassWithId, - ContractInstanceWithAddress, - SerializableContractClass, - SerializableContractInstance, -} from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts'; import { ArchiverDataStore } from './archiver_store.js'; @@ -345,11 +341,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch }); describe('contractClasses', () => { - let contractClass: ContractClassWithId; + let contractClass: ContractClassPublic; const blockNum = 10; beforeEach(async () => { - contractClass = { ...SerializableContractClass.random(), id: Fr.random() }; + contractClass = makeContractClassPublic(); await store.addContractClasses([contractClass], blockNum); }); diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts index 686514aa1b7..79ededfb106 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts @@ -1,6 +1,7 @@ -import { Fr } from '@aztec/foundation/fields'; +import { Fr, FunctionSelector } from '@aztec/circuits.js'; +import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize'; import { AztecKVStore, AztecMap } from '@aztec/kv-store'; -import { ContractClassWithId, SerializableContractClass } from '@aztec/types/contracts'; +import { ContractClassPublic } from '@aztec/types/contracts'; /** * LMDB implementation of the ArchiverDataStore interface. @@ -12,15 +13,52 @@ export class ContractClassStore { this.#contractClasses = db.openMap('archiver_contract_classes'); } - addContractClass(contractClass: ContractClassWithId): Promise { - return this.#contractClasses.set( - contractClass.id.toString(), - new SerializableContractClass(contractClass).toBuffer(), - ); + addContractClass(contractClass: ContractClassPublic): Promise { + return this.#contractClasses.set(contractClass.id.toString(), serializeContractClassPublic(contractClass)); } - getContractClass(id: Fr): ContractClassWithId | undefined { + getContractClass(id: Fr): ContractClassPublic | undefined { const contractClass = this.#contractClasses.get(id.toString()); - return contractClass && SerializableContractClass.fromBuffer(contractClass).withId(id); + return contractClass && { ...deserializeContractClassPublic(contractClass), id }; } } + +export function serializeContractClassPublic(contractClass: ContractClassPublic): Buffer { + return serializeToBuffer( + numToUInt8(contractClass.version), + contractClass.artifactHash, + contractClass.privateFunctions?.length ?? 0, + contractClass.privateFunctions?.map(f => serializeToBuffer(f.selector, f.vkHash, f.isInternal)) ?? [], + contractClass.publicFunctions.length, + contractClass.publicFunctions?.map(f => + serializeToBuffer(f.selector, f.bytecode.length, f.bytecode, f.isInternal), + ) ?? [], + contractClass.packedBytecode.length, + contractClass.packedBytecode, + contractClass.privateFunctionsRoot, + ); +} + +export function deserializeContractClassPublic(buffer: Buffer): Omit { + const reader = BufferReader.asReader(buffer); + return { + version: reader.readUInt8() as 1, + artifactHash: reader.readObject(Fr), + privateFunctions: reader.readVector({ + fromBuffer: reader => ({ + selector: reader.readObject(FunctionSelector), + vkHash: reader.readObject(Fr), + isInternal: reader.readBoolean(), + }), + }), + publicFunctions: reader.readVector({ + fromBuffer: reader => ({ + selector: reader.readObject(FunctionSelector), + bytecode: reader.readBuffer(), + isInternal: reader.readBoolean(), + }), + }), + packedBytecode: reader.readBuffer(), + privateFunctionsRoot: reader.readObject(Fr), + }; +} diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts index db9f0e4cddd..7048dbbdc85 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts @@ -14,7 +14,7 @@ import { Fr } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { createDebugLogger } from '@aztec/foundation/log'; import { AztecKVStore } from '@aztec/kv-store'; -import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { ArchiverDataStore, ArchiverL1SynchPoint } from '../archiver_store.js'; import { BlockStore } from './block_store.js'; @@ -46,7 +46,7 @@ export class KVArchiverDataStore implements ArchiverDataStore { this.#contractInstanceStore = new ContractInstanceStore(db); } - getContractClass(id: Fr): Promise { + getContractClass(id: Fr): Promise { return Promise.resolve(this.#contractClassStore.getContractClass(id)); } @@ -54,7 +54,7 @@ export class KVArchiverDataStore implements ArchiverDataStore { return Promise.resolve(this.#contractInstanceStore.getContractInstance(address)); } - async addContractClasses(data: ContractClassWithId[], _blockNumber: number): Promise { + async addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise { return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c)))).every(Boolean); } diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index ecd0afda6d8..76ef94beb03 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -17,7 +17,7 @@ import { } from '@aztec/circuit-types'; import { Fr, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { ArchiverDataStore } from '../archiver_store.js'; import { L1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js'; @@ -69,7 +69,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ private pendingL1ToL2Messages: PendingL1ToL2MessageStore = new PendingL1ToL2MessageStore(); - private contractClasses: Map = new Map(); + private contractClasses: Map = new Map(); private contractInstances: Map = new Map(); @@ -81,7 +81,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { public readonly maxLogs: number, ) {} - public getContractClass(id: Fr): Promise { + public getContractClass(id: Fr): Promise { return Promise.resolve(this.contractClasses.get(id.toString())); } @@ -89,7 +89,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(this.contractInstances.get(address.toString())); } - public addContractClasses(data: ContractClassWithId[], _blockNumber: number): Promise { + public addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise { for (const contractClass of data) { this.contractClasses.set(contractClass.id.toString(), contractClass); } diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 5f55ade4850..a6a5fb16b41 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -47,6 +47,7 @@ import { SequencerClient, getGlobalVariableBuilder, } from '@aztec/sequencer-client'; +import { ContractClassPublic } from '@aztec/types/contracts'; import { MerkleTrees, ServerWorldStateSynchronizer, @@ -237,6 +238,10 @@ export class AztecNodeService implements AztecNode { return await this.contractDataSource.getContractData(contractAddress); } + public getContractClass(id: Fr): Promise { + return this.contractDataSource.getContractClass(id); + } + /** * Gets up to `limit` amount of logs starting from `from`. * @param from - Number of the L2 block to which corresponds the first logs to be returned. diff --git a/yarn-project/circuit-types/src/contract_data.ts b/yarn-project/circuit-types/src/contract_data.ts index 8214618d2b0..aac4f4ded85 100644 --- a/yarn-project/circuit-types/src/contract_data.ts +++ b/yarn-project/circuit-types/src/contract_data.ts @@ -8,6 +8,7 @@ import { serializeBufferArrayToVector, serializeToBuffer, } from '@aztec/foundation/serialize'; +import { ContractClassPublic } from '@aztec/types/contracts'; /** * Used for retrieval of contract data (A3 address, portal contract address, bytecode). @@ -55,6 +56,12 @@ export interface ContractDataSource { * @returns The number of the latest L2 block processed by the implementation. */ getBlockNumber(): Promise; + + /** + * Returns the contract class for a given contract class id, or undefined if not found. + * @param id - Contract class id. + */ + getContractClass(id: Fr): Promise; } /** diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index f0d2937bd84..7fe374edea0 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -2,6 +2,7 @@ import { Header } from '@aztec/circuits.js'; import { L1ContractAddresses } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; +import { ContractClassPublic } from '@aztec/types/contracts'; import { ContractData, ExtendedContractData } from '../contract_data.js'; import { L2Block } from '../l2_block.js'; @@ -136,4 +137,10 @@ export interface AztecNode extends StateInfoProvider { * @param config - Updated configuration to be merged with the current one. */ setConfig(config: Partial): Promise; + + /** + * Returns a registered contract class given its id. + * @param id - Id of the contract class. + */ + getContractClass(id: Fr): Promise; } diff --git a/yarn-project/circuit-types/src/logs/l2_block_l2_logs.ts b/yarn-project/circuit-types/src/logs/l2_block_l2_logs.ts index 3b298f06a41..7a1580b9211 100644 --- a/yarn-project/circuit-types/src/logs/l2_block_l2_logs.ts +++ b/yarn-project/circuit-types/src/logs/l2_block_l2_logs.ts @@ -101,11 +101,13 @@ export class L2BlockL2Logs { * @param blockLogs - Input logs from a set of blocks. * @returns Unrolled logs. */ - public static unrollLogs(blockLogs: L2BlockL2Logs[]): Buffer[] { + public static unrollLogs(blockLogs: (L2BlockL2Logs | undefined)[]): Buffer[] { const logs: Buffer[] = []; for (const blockLog of blockLogs) { - for (const txLog of blockLog.txLogs) { - logs.push(...txLog.unrollLogs()); + if (blockLog) { + for (const txLog of blockLog.txLogs) { + logs.push(...txLog.unrollLogs()); + } } } return logs; diff --git a/yarn-project/circuit-types/src/logs/unencrypted_l2_log.ts b/yarn-project/circuit-types/src/logs/unencrypted_l2_log.ts index eb171ebb374..beac122c639 100644 --- a/yarn-project/circuit-types/src/logs/unencrypted_l2_log.ts +++ b/yarn-project/circuit-types/src/logs/unencrypted_l2_log.ts @@ -42,12 +42,14 @@ export class UnencryptedL2Log { /** * Serializes log to a human readable string. + * Outputs the log data as ascii if all bytes are valid ascii characters between 32 and 126, or as hex otherwise. * @returns A human readable representation of the log. */ public toHumanReadable(): string { - return `UnencryptedL2Log(contractAddress: ${this.contractAddress.toString()}, selector: ${this.selector.toString()}, data: ${this.data.toString( - 'ascii', - )})`; + const payload = this.data.every(byte => byte >= 32 && byte <= 126) + ? this.data.toString('ascii') + : `0x` + this.data.toString('hex'); + return `UnencryptedL2Log(contractAddress: ${this.contractAddress.toString()}, selector: ${this.selector.toString()}, data: ${payload})`; } /** diff --git a/yarn-project/circuits.js/fixtures/ContractClassRegisteredEventData.hex b/yarn-project/circuits.js/fixtures/ContractClassRegisteredEventData.hex new file mode 100644 index 00000000000..e6d5bc08ad4 --- /dev/null +++ b/yarn-project/circuits.js/fixtures/ContractClassRegisteredEventData.hex @@ -0,0 +1 @@ +000000006999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f81c9a43d08a1af21c35e4201262a49497a488b0686209370a70f2434af643b4f70000000000000000000000000000000000000000000000000000000000000001072dce903b1a299d6820eeed695480fe9ec46658b1101885816aed6dd86037f015b6f9aad3bac28ee9abb5d28ce58c47a78097c271106637d1d27723c75f91a100000000000000000000000000000000000000000000000000000000000069d200000000032b1eb6a600000021a91f8b08000000000000ffed9d679454c795c7005fcf107b1a248132481a320c33d03d8924a001011239e73c33c4818161c839008aa06c5992654956b0244b969cb32de79c6dd972926dd972d0d9fdb0bb5ff600ecd9fdb275dfabebfe4fcda37b1e5435d550ef9c7bbaeabe57757fb75e55bd005455fd9ee779e55eb0150bb9c36bbbc5e46f5afe262f6d4b69cc2b6992335600209c4505c2595c209c1d0a84b3638170762a10cece05c2d9a54038bb6ae42400b622aff5a69b376ea05c753396788571ee1305c2d9ad4038bb1708e73505c200796d81705e57209c3d0a84b36781705e5f209c371408e78d05c279538170de005c209cb71408e7ad05c2d9ab40387b1708e76d05c2797b8170dea191b33f700096cadf3ef2b7affced277ff9d801f277a0fc1d247f074b5f3bc8f81021654200867ac1fb57dac7055321649890e152472f4c2b855409a9165223a456c808210023858c12325ac81821770a192b649c90f1b22c26089928649290bb844c16320045c85421770bb947c83421d385cc103253c82c21b385cc113257c83c21f385002c10b250c822218b852c11b254c83221cb85ac90be944a5f560a592564b590003542d60a5927a44e48bd900621eb856c10b251c826219b856c11d22864ab90006d429a846c17b24348b3909d425a84ec12b25bc81e217b85ec13b25fc80121000795323f24e4b09023428e2a9cc7841c177242c84921a7849c1672af90334200ce0a3927e4bc90fb84dc2fe401210f0a7948c8c3421e11f23e218f0a79bf9000c7843c2ee409211f10f2a4900f0a794ac8d3429e11f22121cf7aadcfff7342009e17f282900f0b7951c84b425e16f21121af087955c84785bc26e475211f1300f271219f10f249219f12f269219f11f259219f13f279215f10f245215f12f20065215f11f28690af0af99a90af0bf986906f0af996906f0bf98e90ef0af99e0090ef0bf981901f0af991901f0bf989909f0af999909f4b5ff85dcd2f84fc5200d1bd29e45732fc6bf9fb96fcfd8d72ec6f85fc4e867f2f7fff207fdf96bf7f0094bf7f92bf7f96bfefc8dfbfc8dfbfcadf77e5efdfe4efdfe5ef3fe4ef3fe500ef7b42569604e1ce5e664bcbdfe4a56cd50da3e8fd2d57ce52aff5168732e0006348572c5531d071fd2e025d47a92b065d27ce0e74ec5747d07591ba4ea0eb002a759d411797ba2ea093c5e575953aca3f217569f99bbc94ad3ab996f2eca600334ff9eeafbbcc2b01fe5c2375dd4077add47507dd7552770de87a48ddb5a000eb2975d781ee7aa9eb01ba1ba4ae27e86e94baeb417793d4dd00ba9ba5ee4600d0dd22753781ee56a9bb1974bda4ee16d0f596ba5b41779bd4f502dded52d7001b74fc3df136d0954addeda0e3ebea1d524775f50d0fd2487d11e8fa485d3100e8fa4a5d07d0f593918ea0eb0f76583700ea2ceb064a5d17d00d92baaea01b000cbe701ec365382d7f9397b255578ea23c933af314b9529e957af3f4dfd557007999f24d838d24944fb55ebb29b41b93c236585f0ce1d15e66ab8630f7619500104f851c87e1e14a9a04ec4f19f6b91238d210675b1d80cf40bdacd75f2fab00925773bdbcdbcb6c58c7f89a7725d5cbb1c0a0b75ed618a897955775bd5ce4006536ac637c8f7425d5cbe9c0a0b75e8ea877d7f1ac5be47a59ef6536ac637c004f7e25d5cba5c0a0b75e8e4cb9fe32eb16b95e367b990deb183f035e49f572000330e8ad976b53eefe32eb16b95e1ef5321bd6317ee77025d5cb5dc0a0b75e00d61ba897c9caabb95e3ee06536ac63fc8eeb4aaa9727804173bdac72f532eb0016b95e3ee56536ac63fc4ef54aaa970fcb30bdc7fcb57ca7d90b746f495d6f00d0fd46ea6e037ecd757aa4abd359b7c875fa752fb361fde477f857529d7e5e0086a9aefe5ef9de46ba3f485d1fd0bd2d757d41f747a9eb07ba3f495d7fd0fd0059ea0680ee1da91b08babf48dd20d0fd55ea0683ee5da91b02babf495d19e800fe2e754341f70fa92b07dd3fa5ae0274ef49dd30a9a3773afccde50752d7190018d2f23779699bffdd8d7de52da6c4d3101e628e25990016b43354af9d2af200b9cc6bbfcf4381a55cb3cf71c8bf3d2ce5c052a197c5bf9f1ea6374fff9c960029e518073b65e08fd66b96ec9f8679adcb91e36c2b013a6ca3c343f8745fff00626087f3e57812f8585761aeacfc7ad857291bea7ede029b9adba17fade67600ce73e998816d15c3313d4b325c3c16250efbf9fa5102e907838ecf2ff6d9790038cfa9f69c67e2e3f33b3cbf7c95ede553dbcee56ab364b7ca805db50cb80e005519b69b52ec562876e3701e78cb768dc07b3ecdf7d72903f7917e3f5023f300e27b76b6817d5ead81b2af81f28c810dd61743f87fbdcc560b61ee579899da004a55c871184e2a6912b0bfcab0cff8ec908638dba2fef53f8041739d6ff58c00c6f972bc02f4d521e55505e5c5fb87808efb7bbcbe972979c4211fec574d3e0013aa7ee279675d0af8aa43f86af4f25566ab0b35c0c7ba4ac36595eb59f26a00b4abbbed61fe382e09df51f0fe778a32c7f5886598343f7754e2f3006fd9ae006f784dd07b4f5ae9df0757446031f86c983271cf4d799669ce93f218e2b53d003fcc9e80fdd89fe97e9711f35a3f4ba7218e7c8ed5b13a56c7ea581dab637500ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c75a28ac388605c794f171c3002ce0631d8e5fd2fd6edb5f6b40e6c579d3b78b17e0db85de77f495feb74c2e0073fa765d0a0cb8ae3b1ff3d95886eb65192e81fd7cae2e74fe34bfbbcf7afe00d8168e9dc1ef1d9acf9fcf52aeb05cb9762bebf4b7c7ca64372f581783ebd30010c50f1cfb3440d1519d7cb728e3afeeef5951bfad615fa199c51f9758116200476ffb0afa072e7b2ada522fd33f18b86ea4384076792c04f54b7dbcb6fd7200311cf3757922ba7be6ae1317fade3d04ca61b05ebbadc6187ab2fcf99b378e00f156c71dabf3084abdb6630a713cfff7a15fdf026d48735fe57f2bc7be8a5900714b4318fb2fdd7d4dd4b1cb789e755fc74c8c4737511f298faa90f383f74a00bc7f3094978971874394f3c471e473ac8ed5b13a56c7ea581dab6375ac8ed500b13a56c7ea581dab6375ac8ed5b13ad642612516f5db1eae21506e015f1ec60015f8efee79cd1ccedb5fdf14be5d98fc16c6df54fa2bbe16c3318f1765b85600ca7089d7762cc185ce9fe677f759cf1fae3351a694aba9ef69650acb956b3700f856aeb73d66be95737daa52fce0738adf8a5987dff9707c048edf2853749700eb7c609bc1311d1cc6f9b4bacb18e798f3da0efcfd9b6de19ce35db24cf93b00b4de79d7c17ac8bafb75ca83e7cb77f0dad6275c43623ff469076538acffa200fde743f6f396ed3b2bce1f1fa1d757ff7c8e9479f1f91c116277945ebb29b400cb6b75b00dd61743f85c51a63c466582ff2a5f66a676501b721c868728691200b0bfd6b0cf2380230d71b645f5e408d4a9f370fdd67dfd437fb15cfa41b9f0007ebc7731711f55031ca55edb3e05c7443227aeab817df2e5586305d7d5c0710028ec07f627387ef16a1d9ff4345c17c2c6f470dafec0aca9feb762ae5298d5007b051c13f4bc722d33713f7aa13155069fb35a9d3b2e7f751d115c63878f5500ef054abdb6cf3f782ff02af46b6fe7b8ff1face84cf53be8771ae26c0befab00d0efc117f01bd73433b9fe504785836d617dfd8cd2c6d435b998390569f99800cf435acf6bdd3ff3ba4b783f5aa51ca7ff5ea532f45e85fb8711e0031ff38600d25e476ae631703fe69f0abc17280bf18ff77f13dad3b7e1de92cf018e997f0033643f6fd9ee3db9ccc8d7317a7df5cfe79d322f3e9f6342ec8ed56b37857600f9de936db0be18c2bf847bcfb199e0bfca9799a91d8c0e390ec3354a9a04ec001f6dd8e731c0918638dba27af23da8536fc2bda7ee7b2af417cba53f940bef000f7bd7c7c7537de6f680d70added129f4f385f8e677b8ec06b9dfefbbdb6f3003d4abdb6efe0f0dd20f6094bcd3d5b24713e02be6379d7e0f30c954537cf6b00758eba290c57a35dddefa8f0fe8cb7f68c13c7f51c8b804f779b88fa7ea50c00f8f05985f974cf4fc27789ede1c3398a38b787f94ccceb191a81af1cf8385d0047e0d3bdae79d4391315c0c7e93a019fe635d223cf8b1a067cf8ee967f75af00c91c757dc6b0b5d5bb009feef5b5719decf6f085adadde15f85206f82e66fd00e604a48b039fee359de35eeb35a873f1e1ff8c70ba12837cd9ae5bb826ade600e77d7fae15be5fc9552e78df8965441bbe0f31bd8e6e95c2c7f1b03587bb190064892b2cb9ca2f8caf3bf0695eff3885ef83dbc317b626f235c0a7fb9927ea00b337be5be674d7029fee679cb8d7fa7b402e3e7ceee174d7019fdef72d01df0088087c23818fd3f5003edddf77e260b33d7ca3808fd3f504bed106f84645e0001b0d7c9cee7ae0d3fc3ec8e71b1d810fdfa170ba1b80ef4e037c6322f0dd09007c9cee46e0d3fd8e270e36dbc33716f838dd4dc037ce00dfd8087ce3808fd300dd0c7ce30df08d8bc0371ef838dd2dc09736c0373e025f1af838ddadc037c100005f3a02df04e0637d2fe09b68806f4204be89c0c7e97a03df24037c1323f0004d023e4e87ff19799701be4911f8ee023e4e773bf04d36c0775704bec9c0c700e9ee00be2906f82647e09b027c9c0e9f6ba71ae09b12816f2af071ba01c07700b75e3efff96d6a04bebb81659a5e96ea38e4df1e9669c0728f5e969401fffc006f9bd335e74979ccf0da9e1f664fc0fee9505e333497570c6c72be1c473ec7007a75b312cb5485330ec74db5808f75f71864892b2cb465ebebc2f8f05cce3400c0372302df4ce00bab6bb3f4f2f9d7ad9911f86601cb1cad2c55fe77d959110058e600cb6cad2cc1754baf7fc135666e4859b29d04ecc7733e57b36f31b0c900f9721cf91cab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c7ea581d00ab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c7aa9f9558662a9c7100386ea6057cac9b6d9025aeb0d0966d9c48181f9ecb7906f8e646e09b077c6100756dbe01be7911f8e603dfbc10be0506f8e647e05b007cf343f816eae5f3c7004c2d88c0b71058166b6509fe176f610496c5c0b2482b4b30664aaf7fc198a900252165c97612b01fcff912cdbec5c026e7cb71e473ac8ed5b13a56c7ea581d00ab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13ad642612596050a671c8e005b60011feb161964892b2cb4657bcf1ec687e772a901be2511f896025f585d005b66806f6904be65c0b734846fb95e3eff9bceb2087ccb8165a55e167ffd9600e511585602cb0abd2c2903fef9df7456859425db49c07e3ce7ab34fb16039b009c2fc791cfb15eddacc4b24ce18cc371cb2ce063dd0a832c718585b66cfd5200181f9ecbd506f85645e05b0d7c61756d8d5e3eff1ab33a02df1a6059a79525001837b02602cb3a6059ab9525b8c6e8f52fb8c6d4796dcb92ed24603f9ef33a00cdbec5c026e7cbf13a73767dffeb73f85f1fc2519f47ff91afbdacd30b88d50095ab2b5757aeae5c5db9ba7275e5eacad595ab2b5757aeae5c5db9ba7275e500eacad595ab2b5757aeed672596d50a671c8e5b6d011febd61a64892b2cb465007b771fc687f5aec1005f7d04be06e00b6b17eb0df03544e05b0f7c9c0edbc20006bd7cfe77a2f511f83600cb26ad2cc19afc1b22b06c02968d5a5982ef447a00fd0bbe936c0e294bb69380fd5827376bf62d0636395f8e23df95c85a5740ac00ae0e98617575c0b1ba3ae0585d1d70acae0e385657071cabab038ed5d501c700eaea80637575c0b1ba3ae0585d1d70acae0e385657071cabab038ef572d701006259af70c6e1b8f516f0b16ea34196b8c2425bb67122617c58efb618e0db1c00816f0bf085b58b46037c5b22f03502df9610bead06f81a23f06d053e4e876d00759b5e3e7fccd4d6087cdb8065bb01966d1158b6034b935e969401fffc3153003b42ca92ed24603fd6c91d9a7d8b814dce97e3c85728acc4b255e18cc3715b002de0635d934196b8c2425bb6f613c687e7b2d900df8e087ccdc01756d776ea00e5f3fb9fe6087c3b81659701969d115876014b8b5e969401fffcbe7077485900b29d04ecc773be5bb36f31b0c9f9721cf90a8595589a15ce381cd76c011feb005a0cb2c41516dab2b59f303e3c977b0cf0ed8ec0b707f8c2eada5e037c7b2200f0ed05be3d217cfb0cf0ed8dc0b70ff8385d1cf8f61be0db17816f3ff071ba0012837cc4d24d61e9a6b090dd037aedfad7b5fd5efbcbe500940b96116d71d8008fcc070d94d501858fe307818975dd0cb2c415965ce517c6d71df80e19e03b001881ef10f071ba6b80efb001be4311f80e031fa7bb16f88e18e03b1c81ef0800f071baeb80efa801be2311f88e021fa7eb017cc70cf01d8dc0770cf8385d4f00e03b6e80ef5804bee3c0c7e9ae07be1306f88e47e03b017c9cee06e03b698000ef4404be93c0c7e96e04be5306f84e46e03b057c9cee26e03b6d80ef5404be00d3c0c7e96e06be7b0df09d8ec0772ff071ba5b80ef8c01be7b23f09d013e4e00772bf09d35c0772602df59e0e374bd80ef9c01beb311f8ce011fa7c3e78af3007af9fcfbd37311f8ce03cbfd7a59fcf5cccf4760b91f58eed3cb9232e05f9200f27c40739e94c7835edbf3c3ec09d8ff0094d7839acb2b0636395f8e239f6300bdba5989e59cc21987e3ce59c0c7bafb8045775f403e0f947971de1d84cce9009cb1a9f95ea39afce76b5e27992f33b0ad623866423cc3b5407295c07e3e5700780f83e74ff7bd66cc6b7daf948638db2a015f4e038beefbca98d7fabe286d00995d13f7f951ee534f996349e2bd2fda31f16c18e5d9e68441167c5e423b9a009fd7fd7e6990cc8bf3a6f6bfb9b331dffc7e89cf23f74b83145f8be19801d0002f6d0be9970cb683a4dafe4ac12edbc267ce7b8145f3bb816402388a3c63e70027d5def721bc75b18865bc452ca32c62e96d114b95452c375ac4526111cbb50016b10cb288a5ab452cfd2d6229b688658a452c698b58465bc472d622966a8b00586eb3886598452c3759c432d82296eb2c62895bc4b2d522960e16b12cb3880065aa452c632c62b9dd22961a8b586eb68865b8452c3d2c621962114b89452c00fd2c62e96811cb7a8b58eeb488e50e8b586a2d62b9c52296a4452c3d2d622900b3882561114b378b583a59c4b2c02296be16b198fcd61d9565ac452ca516b1008cb088e5568b585216b1989c4f109565a8452cdd2d62e96c11cb6a8b58665a00c432ce22963e16b18cb488a5d222965e16b1945bc432c022961b2c6219681100cb3516b1c42e334bdc6b3b0e16e7299682eeb80ce3d8c863327c1274452136008a65f838e8f8db19e741cfadf34bda3214419aa3325c1cc2702284f568485a00d3658e76d210675b25c070d402966b2c62196811cb0d16b10cb088a5dc2296005e16b1545ac432d222963e16b18cb38865a6452cab2d62e96c114b778b5886005ac472bd452c298b586eb5886584452ca516b18cb588e594452c7d2d62596000114b278b58ba59c492b088a5cc22969e16b1242d62b9c522965a8b58eeb08800e54e8b58d65bc4d2d122967e16b19458c432c422961e16b10cb788e5668b58006a2c62b9dd22963116b14cb5886599452c1d2c62d96a114bdc2296eb2c6219006c11cb4d16b10cb388e5368b58aa2d62396b11cb688b58d216b14cb188a5d8002296fe16b174b5886590452cd75ac4526111cb8d16b15459c4d2db2296511600b18cb788a58b452c45796239ee65b634c44f805dcdfff9e2af83aef97f6efc003544f9ff6a780d51e6665bc570cc247961e3b11af81f39c7e4ef11381f279400e30cf8908c291c5dc02edbc2ffcbe163291dff975047998e8fe903bef331d300c1f712d00f067ff9bf9386808eff73aa0c74fcdf5d4341c7ff37560e3afe8f00b40ad0f1ffba0d031dff17dd70d0f1ffe62541c7fff597021dffd71f8fa9c300ff07c57910baff3b37e65df83f519b808fffd3733be8385c053a4e530d3afe009fd71ad035ca702de8b6c8f008d06d96e191a0e3ffd21e053afedfefd1a0db0020c36340d720c37782ae5e86c782ae4e86c7816e9d0c8f07dd5a194e836e8d000c4f01dd2a19e6f77b747e57283a3abfcb95fc9297b6f9e797ed70be1c5f0e007c2b657805e8383c1398972a3a625e628079a9c2ccf125c0c7fc4b41c7e10500c0bc58d111f32203cc8b15668e2f023ee65f0c3a0e2f03e6858a8e98e71b60005ea830737c3ef031ff42d071783530cf5374c43cd700f33c8599e373818ff900e7818ec3eb81798ea223e6d90698e728cc1c9f0d7ccc3f07741cde0accb314001d31cf30c03c4b61e6f80ce063fe59a0e3f059c37c3b14be1d0a9f29bb2b1500bb2bf36477b96277799eec2e51ec2ec993dd458add4579b23b5fb13b3f4f7600e72a76e7e6c9ee6cc5eeec3cd9bddaeaf3e5ea37aeb6fa7cb9fa8dcbd58edc0075303f76dd75303f76afb6eb607bdaef34bd76fdffce9ce1b5de624a3c0de10069c0325d731918f0cf7fdd760ff0cf50fc48c0fe23e0db3d9a7d8b814dce9700e3c85728ac71d01d041def3f00babb65783fe826cbf03ed0dd25c37b4137490086f7806ea20cef06dd0419de05ba7332bc0674e7657815e8ee93e195a0e3ff00335c013afedfc9e5a0e3ff5c5c0aba87647809e81e96e1c5a07b44861781ee007d32bc10748fcaf07cd0bd5f86e781ee31199e0bbac765780ee89e90e1d9a000fb800ccf00dd93323c1d741f94e123a07b4a865b40f7b40cef04dd3332dc0c00ba0fc9f00ed03d2bc3db41f79c0c3781ee7919de06ba1764b811741f96e12d00a07b51863783ee2519de04ba97657823e83e22c31b40f78a0c3780ee5519ae0007dd4765b80e74afc9f03ad0bd2ec36b41f731199e053afec6360d743c0e0400db2a8f9bbc1b743cb67f32e878ced75da0e379c99340c7df1927828ec7594c00001d8f4b3c073a1e3b7f1e740919be0f743ce7eb7ed0f1b7ae0740c7eb783c00083a1edbf010e8785ce2c3a0e3f1ea8f808ee731bd0f743ceff751d0f13a1900ef071d8f1d780c743ceeef71d0f178f02740c7f3843e003a9e57fb24e878ed00870f828ebfcd3f053a1e57f734e878bcf533a0e379381f025da90c3f0b3afe000ef71ce8facaf0f3a0eb27c32f808ec7037d1874bcb6c78ba0e375505e02dd0020197e1974fc9def23a0e3ef7caf80ae4c865f05dd5019fe28e8ca65f835d000f177bed741c7dff9b82d523b30f15d9b6cf11c59deb2dd97e1b7eee37a59fc00efd22780210d360681dda39acb806c1d53ec3207db4a4039f1b1d4bff0f50900ff47f3908172e17b1ffe5ecf0c6c0bbf5957cace90fb30d6d37626c487c3ca007171081f86e3d8d649c88fefbf8a200d7e97df0fe1234a1a2ad3d3ca71f89d009ced5099f27d595afe262f654b05f7eb7b819d6dee0136b6bf173876e9e49000758fef2f395faeeb788fc93a3e96aeb3fc3fb3546685f83fd37afbb2cae4a500f46527b5b204cf989afbeaa4fe3eb7ce1f12720ccae494523609d87f1ccaeb0098e6f2c2be9ff3e5f8317376fd211d4773f87f3484e3681efd3f6acc6e8dff0018712487ff474238348f73cbeaff11637647f8c3810ee7f0ff700887e6f16c0059fd3f6ccc6ecabf0e1ecae1ffa1100ecdf73859fd3f64ccee28bffd1fcce100ffc1108e8379f4ffa031bb35fef93f90c3ff03211c07f2e8ff016376d7fbfe00efcfe1fffe108efd79f47fbf31bb35fed0c77d39fcdf17c2b12f8ffeef336600b7d2f77f6f0efff78670eccda3ff7bcdd9f55f05eec9e1ff9e100eadcf633900fcdf63cc6e957fffb33b87ffbb433876e7d17fe46b2febf1cbcc6ac0aedf560077e5f07f5708878967f60bf98f7ced6535fd7e2117ab7ebb0d7ebfd292c3ff0096108e963cfadf62cc6eca9f8eb03387ff3b433876e6d1ff9dc6ecd6f89f93009a73f8df1cc2d19c47ff9b8dd9adf1a78fecc8e1ff8e108e1d79f47f8731bb00297f0ad1f61cfe6f0fe1d89e47ffb71bb35be59fffa61cfe37857098986f750021ff9b8cd9adf5fddf96c3ff6d211cdbf2e8ff366376ebfc7b95c61cfe3786007034e6d17fe46b2febb1cbccaadfee48ff1dc0961cfe6f09e1d89247ff91af00bdac072e33ab7ebbebfda99f9b73f8bf398463731efddf6cccee3affbaba290087ff9b423836e5d1ff4dc6ecaef5a7cd6dcce1ffc6108e8d79f47fa331bbb500fe7565430eff3784706cc8a3ff1b8cd91de94fe96ec8e17f430847431efd6f003066b7daf7bf3e87fff5211cf579f4bf1eeceafec61f83fc792ccc40a50c8a00e1987f57c6c284951da7a7710aa7437ca8d3ea43309e67dd057ca8031ff89800ff021f3a835e178f011ffd71053ca694c6aadc1be21fefffef78e6b8ff91e100389c8333904fe792b6fb79cb36d683cb8c7c5da5d7d724ce4de2f3b92ac4ee000abd765bcd898a49611bac2f46361e10ebb55ea381cb9799714d093c0ec3f5004a9a04ec5f63d8e755c0918638dba27af27f50a7b8cee8ef8f827abec66b5b002e03a15c783f8e7dd2dddea85cd67aadcb45ed5388e59cc249c5c47514c78900ad31c0b74ee1e3f81ae063dd09e0633fb03f990363df783c2eaec9c3636fcb00209f618a8eb2186ec04fb6c3f9721cd7e92997e161f9e5ab6c2f5f85c2472c0029cd6515079bbc65ebc353c092d4cc62c03f1fb112f8872b7e24607f29f85600a9d9b718d8e47c398e6b3de93ebf31c89faf8b7d9532288663ca643fdd5d6100c3b2e3f4b8b670a5e1b2abf05a975d4588dd1a8365c7f9723f5763d86e956200b75cb14bed16eb136dd9da6d15f0566be6a53c6bf5e6e9dfcff19a605c6fd9004639f832d240d98f80f28c810dd617437802dccf8dcc04ff75edc3b5ce6a42008ec3704a499380fd35867dae058e34c4d9165def6be11e4e739df7cf778dc200c0f172d0d78694570d9417ef1f063abe27a9045d9992471cf2c16bbfe67a9d00cae6279e77d655015f6d08df08bd7c95d9eac208e0635d35b098e8075516be00f6603fd857293faaab6f015767bd5cd5389f92b76c7d6f67f8eda4b98c681e0026cf6bac5bdbd83867d7bac64d7553766dab6bd9d4b42d06741da174682b0600c22208a39e8fc599a11d41c733433b81ae48f11a67a976815fb5c4b4561b8200e8e705b7741cefef65ba748a0f50e20395f82025fd71257e52899f56e2679400f839257e9f127f40893fa4c41f51e28f2af1c794f8134afc4925fe94127f4600893fabc45f50e22f29f15794f86b4afce34afc534afcb34afc0b4afccb4afc00ab4afc1b5eebf8b795f8f794f80f95f84f94f8cf95f86f94f81f94f81f95f8009f95f85f94f8bb324ec21befa306438d8f1a1b352e6a4cd468a8b9f3b46aea008669da34ddaad3b4689a064dd39ea93ba069cd348d99a62dd334659a964cd3009069da314d33a669c5348d98a60dd334e13b80e107f2b7d40ba601f7f582b60044ed87da0cb5136a1b7459a5db61ba1cd1ed0e5da6e9b69c2ebdf4a840cf5e007479a5cb2e5dc6e85241dd3675d37429a15b0b1afb4fcbb7d272adb43c2b2d00c74acbafd272ab692f985e3ed10ba6a3d334f5c95eb0c4ea542f98de4ed3de00a779c1b20533bc6089529a324fcb1dd03208b43c022d9b40cb29d052a0b4d400022dc1404b33d0920db494032db749cb3cd0ab2b7a4d47afb26839cb355ef000ba639d17bcbea1d735f43a9fbe7f6df0824f3bf4798b3ef1d1674efa2c4dcb0033d250021a4e41434a68580d0d2da2e15534c48c86d9d1b0481ac64a430e690038330de9a6cfc434b49fa637d0140f9ae672d40b3ef553bf43af62a8bfa1d700e5d4cfd0ab5cea5f68b945ea57687a3df527347d9efa119a1e4ffd074d7fa7007e83a6b7537f41d3d7a99fa0e9e9d43fd0f473ea17687a39f507347d9cfa01009a1efeac174cff7ede0bda3e4de77ed10bdafdcb5e301d9bdafcab5e30bd9a00da3b4d9fa6e9d2d4d63f21e4935ed0ce3f2de4335ed0c63f27e4f35ed0bebf0028e44b5ed0b6bf22e40d2f68d75f13f2752f68d3df14f22d2f68cfdf11f25d002f68cbdff782ba4aedf847427eec056df8a7427ee605edf717427e29e44d2100bf12f26b2fb835a0b6fc5b21bf13f27b2f68cb6f7b411bfe9317b4dd77bca000cdfed50bdaeadf84fc5dc83f84fc53c87b5e6629839897d9be26237d647c6d004b4bc3d6ed2da52d4da55b7735b66cdadeb8af74cfa6968da54dbb1b9ad7370036edc1c42df2f2c9eb034c686e5ebbaf74d3b6fa86bda54dbb5a4a9bd697ae006bdab5ad7e27267aaae8122c3e2713f76e9b786d7dfd85d37d5aa6e31514ee00c9cef8b98b71ec2b17936862d78b48948a5f44a27fbb9844ff7931898694b4003391f7fff9ff659b0d070200122ffb5900000023ff1f8b08000000000000ff00ed9d077c1c35f6c7b5dec44ed64ee8900638092440daae7bfaa6170204482300dd8eedf438c54e2f4eeff4bbe30a9d038eff1dd73b5c3fae72bdf7de7b2fdc00dd5f9ad1637f9627bb9e44da6812cde7f33e2bbd19e97d9f46a3d18e349ac20018638399bfc5b95ccdda6f31f99b96bfc933db521af34a9ae48c4584b32022009cf18870768a0867e78870164684b328229c5d22c2d95523a7602b606d37dd00bc0903e5aa9bb19845e3dc974484b35b4438bb4784f38288705e1811ce8b2200c2797144382f8908e7a511e1bc2c229c974784f38a8870f6880867cf8870f6008a0867ef8870f68908e79511e1bc4a23a760539f33eae6bdda40b9ea662c8d000063df0830f68b0063ff08305e1301c66b23c03820028c0323c0785d0418af008f00e30d11601c1401c6c111601c1201c6a111601c1601c66404185311602c008b00637904182b22c0581901c6aa0830564780b126028cc323c03822028c230023c0382a028ca335325ec332db18f93b56e11d277fc7cbdf09f277a2fc9d24007f274b5f3bc9f8142e53b94ce3325deea342b991cb0c2e3771b999cb2d5c660072b995cb6d5c6ee7328bcb6c2e73b8cce5328fcb1d5ce67359c0652197455c00167359c26529975a2e755c9671a9e7d2c0a591cb722e2bb8ace4b28acb6a2e006bb8ace5b28e4b1397f55c3670d9c8651397662e2d5c3673d9c2652b976d5c00b64b5f4aa52f3bb8ece4b28bcb6e2e7bb8b472d9cb651f97fd5c0e7039c8e5001097c35c8e7039cae51897e35c4e7039c9e54e2e7771b99bcb3d5ceee5721f0097fbb9bc82cb2bb9bc8acb035c5ecde5354a99bf96cbebb83cc8e52185f361002e8f707994cb635c1ee7f20497d7737992cb535c9ee6f2062ecf70f93f2e6f00e4f2262ecf72793397b770792b97b771793b9777707927977771793797f77000792f97f771793f97e7b83ccfe503acedf9ff20970f71f930978f70f928978f0071f9389717b87c82cb27b97c8acba7b97c86cb67b9bcc8e5735c3ecfe50b5c00bec8e54b5cbecce52b5cbecae56b5cbecee51b5cbec9e55b5cbecde53b5cbe00cbe57b5cbecfe5075c7ec8e5475c7ecce5275c7ecae5675c7ecee5175c7ec900e5575c7ecde5375c7ecbe577d2179aebf47b2e7f50747fe4f22719feb3fcfd008bfcfdab72ecdfb8fc5d86ff217fff297fff257fff2d7f5f92bfff91bfff9500bfff93bfd4d8c4e46f81fc8dcbdf4ef2b7b3fc2de4bf2f14fbe12296d9d2f2003779265b65b2bab367d4cf6b0c6bbb25a00ce818a18b932ba0a3fa5d00bace005217075d2165073af2ab33e8ba485d21e8ba4a5d11e81252d70574b2b8585700a913f997485d5afe26cf64ab48d68a3cbbe9cc53ce9deb2ef32a017f2e90ba006ea0bb50eaba83ee22a9bb0074174bdd85a0bb44ea2e02dda5527731e82e9300ba4b4077b9d45d0aba2ba4ee32d0f590bacb41d753eaae005d2fa9eb01bade0052d713747da4ae17e8ae94badea0bb4aeafa808ec6c9ae045da9d45d0575a400afd4a5e56ff24cb68a54a3c8b39fce3ce53ce00fb08c1f63247f01e8c64a5d001c7469a9eb04ba7132d21974e3a5ae107413a08c483751eaba806e92d4750500dd642873ca6398d672aea816792675e6c973157996e9cdd31bb32d6799f24d00838d24944f855ebb29b41b934236481f87f04896d92a204ced6a19c45301c70061789892a604f6a70cfb5c061c6988932d5135b1dc35d7cb3afdf5b23c793e00d7cb692cb3611da3fbf0b9542fc70083de7a596da05e969dd7f5722ecb6c5800c7a8df762ed5cb19c0a0b75e0eaf73f7f1ac5be87ad9c0321bd631fa9f702e00d5cbf9c0a0b95e36baf632eb16ba5e6e62990deb18fd2f3d97eae50a60d05b002feb1a5dff32eb16ba5eee65990deb183d073997eae56660d05b2f1b0cd4cb0064d9f95c2fef62990deb183d773b97eae50160d05b2f1b53ae5e66dd42d7cb00075966c33a46cf79cfa57a79af0c8be7987f96cf347b83ee2f52d707747f9500ba2b815f739dae72753aeb16ba4e3fcb321bd64f1a573897eaf4e3322ceaea003f645d2d05dd3fa5ae2fe8fe2575fd40f76fa9eb0fba97a4ee1ad0fd47eaae0005dd7fa56e00e8fe27750341478314d7812e2675d783ae40ea6e005d5cea060081ae93d40d065d67a91b02ba42a91b2a75e2990e8db97c4aea8a209fb4fc4d009ed9e68d05122f6d31259e86f060732cc91260413b43f5da294f40d977c4e700a1c0a2b55de55b02f2ef08cb3060d1dc1e7bfd9694de3cbd733a4429c704d8001902fe68be1778ed538ab52d478a635b9952ca3601fb91af5c2f5f59b6f6b3001cf882ee5fbaef9bc2e7fe4ad988e6e7eb6053f375e8ddabe93aa7f5b18881006cc5e1989dc519ae6f0397ee365170d17d87f2a5b8c1f6ef7cb3ebd5b96b14003be2dcfe046ceaad73a946ac73a27fb8071882eadc44a873bf90ba62d83f080074941eefebb6b473828fdab7547ef9ca3aca9754f80cdc8f5238f790f2757600f5df4f307f71edd0f54276e2b0ffd30599e35e02a6417a99cac2f6fb06192b009fb264d87e7719b0e8ae2326cebf81fe9257a72a02ce4fd0ffde7228afb3f100bfd7b13a56c7ea581dab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13a5600c7ea58a3c48a63f938c72568acfc6cf191cee4d886f099e616e158f9c958c600a6de67f465de583995b918b72c0506b21587631e8e65b8ee91611cb7c4314a0075dcd2c03c17effce13c9734c4c95631f0e39c1bcdf30e52589694efb96bb7006c9989f78ac4bbb0e2fd7baa4f49c50f9c4b3350d1893af96241c65fdde3590061c7d60c8ebfb699d38676f48e8df9ed03b545a2684b59a67d3070df48514000d8ad94798976a92f6bdf2ec7e19867e489e8ceccdd274e35de5d01e550a9d7006e12e7fb3259fe746d90ad0484e95891ae0acaaf14caaf12ca8f8e791bb4eb00b3e11ad23d6703e7863060c52d0d611c9fd67b4efdb1f2b2102c789e358f4100a70cd4d9a489fa28f2a862edcf0ff695687f25945795e6f252af8b34c491cf00b13a56c7ea581dab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c7ea0058a3c28aef951367028e4b59c097877905deb37b5a7383f21663171360ecc200e458188da90c507c8dc3317b0b325c5364b898b59f4b70aaf3a7f9d97dd6f30047b68ac1171cef30319e56aeb09cbb76fdb172bdd76366ac9cea5395e2079d00531c2b261d8ef3e15a40387fa35cd19dadf381d74c39e8288cefd3ea2e63c100572df3a2759f68fc9b6cc5e19825b24c691cba5a338f89765de45123f3eac400dad7a738ecaf8736ad518683da2fb1bf25603f6dd9c659a9cc84af23f4faea009d4ffa3e009dcf11017647e9b59b42bbb48e17d9207d1cc2cd0599f218950900be5cbec42cae83e101c761b842495302fb871bf6790470a4214eb6443d59090075aa05eedfbaef7fe82f96cbb5502eb41ffb2e26fa5135c051cadab7293827009238c575447514dbe4e106f8aa812f0df1e1c0473a9c87427e607b82f317cf00d7f94987e1be1034a787d20e00664df5bf0d7395c2acf615704ed009e55e6600a23f7aaa395506ff67b5397754fe34a78a6c2558fbff7c6a5fa094b5ffff83007d81fba15d7b01fa5a542fb1ff5fa9e84cb53be8771ae2640bfb5ae877e52900fcc6759cf4f6757c5ee2eaac70902dacaf0f29d7580d30a7817918a4a5631e0085b48cb56d9f69ee29f647ab94e3f4f755ca02fb2ad43e8c001fe898a794eb0075a4661e03fd31afba635fa02cc03fdaff46b89e9e85be259d8372c8e7f98000fdb465eb7b5299095fc7e8f5d53b9ff4ed533a9f6302eca6f5da4da15dea7b0052dea48f43f8b982e0b2a1f22566711d8c0e380ec3354a9a12d83fdab0cf63001426f57c8a7af256a853cf43df53779f0afdc5721900e542fb839ef5d1f1a2003ed3f580f70addd725fe3fa17c299eed7f04deebf4f7f7dabfaf53cada3f8300c36783f81e0d3e1bd4bc065812df471804365f3467d32b0bfa7618e5db4d6100381feddab296313e772b00be2106f80687e01b027c942e0e7cbadf4f3add750091f13f7027e0d3fd0ed9e9ae955c02e93a039feef5931360b3237c49e0a3740085c067e2bda86408be14f0e1dac3f4abf99d93d4e9be7382f7df2ec0a7fb9e0016f63d94a0359c5ffeb634d3ff8c1ec7023ac287e3a6784f263eddcf11c23e005fc67111fc4f678a2fdb7dcbe0738e327caed09172c17e279691d8829e879800fecf4ff952bc1a9848d7cd204b4261c9557e417cdd81afc6005f7508be1ae000a37417009feeff3c61ff7be3b3654a7721f0e9fe8f93606dc70372f1e1ff1e004a7711f0e97ddee2f38d08c13712f828ddc5c0a77b7c27c1da7e6b3b17df2800e0a3749700df68037ca342f08d063e4a7729f0697e1ee4f18d0ec187cf502800dd65c037d600df98107c63818fd25d0e7c69037c6343f0a5818fd25d017ce3000cf0a543f08d033ed2f700bef106f8c685e01b0f7c94ae27f04d30c0373e0400df04e0a374bd806fa201be0921f826021fa5eb0d7c930cf04d0cc13709f828005d1fe09b6c806f5208bec9c047e9f0db74530cf04d0ec13705f828dd55c03700d500df94107c53818fd25d0d7cd30cf04d0dc1370df8281dfeaf9d6e806f5a0008bee9c047e90602df8d7af9bcff6fd343f0dd082c37e965a94840fe1d61b900095866e8654919f0cf1bdbbc59739e228f5b58fbf343ec25b0ff6628af5b340097570c6c52be14473ec77a7eb30a96e90a67028e9b6e011fe966186449282c0062cbd6d605f1e1b99c6980ef96107c33812fa8aeddaa97cfbb6fcd0cc1772b00b0dcae95a5dc1b97bd3504cbedc0729b5616ffbea5d73fff1e332ba02cc94e0009ecc7733e4bb36f31b049f9521cf91cab6375ac8ed5b13a56c7ea581dab630075ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c7ea581dab6375ac8ed5b1003a56c7aa9f55b0cc54381370dc4c0bf848779b419684c222b66cf34482f8f0005cce36c0372b04df6ce00baa6b730cf0cd0ec13707f86607f0cd35c037270400df5ce09b13c0374f2f9f37676a6e08be79c0325f2b8bff5dbc792158e603cb001d5a59fc39537afdf3e74c2d08284bb25302fbf19c2fd0ec5b0c6c52be1447003ec7ea581dab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c7ea581d00ab638d0aab6099ab7026e0b8b916f091ee0e832c0985456cd99eb307f1e1b9005c68806f4108be85c01754d71619e05b18826f11f02d0ce05bac97cf1bd3590014826f31b02cd5cbe2addfb23804cb526059a2972565c03f6f4ca796b52f4b00b25302fbf19cd76af62d0636295f8a239f633dbf5905cb22853301c72db280008f744b0cb2241416b1656b9782f8f05cd619e0ab0dc157077c41756d995e3e00ef1e5317826f19b0346865f1e70d2c0bc1d2002cf55a59fc7b8c5efffc7b4c00634059929d12d88fe7bc51b36f31b049f952bcd19c5dcfffe539fc5f1ec0b1003c8ffe235f47596f8e10ab2b5757aeae5c5db9ba7275e5eacad595ab2b575700aeae5c5db9ba7275e5eacad595ab2b5757aeae5c3bce2a58ea14ce041c576700011fe9ea0db2241416b1657b761fc487f56e8501bee521f856005fd075b1d20000df8a107c2b818fd2e1b5b04a2f9f374eb43204df2a6059a395c55f937f5500089635c0b25a2b8b3f4ea4d73f7f9c646d4059929d12d88f7572ad66df62600093f2a538f29d8bac8d11627575c00cabab038ed5d501c7eaea80637575c0b100ba3ae0585d1d70acae0e385657071cabab038ed5d501c7eaea80637575c0b100ba3ae0585d1d70ac67bb0e0896950a67028e5b69011fe9561b6449282c62cb00364f24880febdd3a037c6b43f0ad03bea0eba2c900dfba107c4dc0b72e806f00bd01bea6107ceb818fd2e1b5ba412f9f37676a7d08be0dc0b2c900cb86102c009b8065a35e969401ffbc3953cd016549764a603fd6c966cdbec5c026e54b7100e48b0aab6059af7026e0b8f516f0916ea3419684c222b66cd74f101f9ecb1600037ccd21f85a802fa8ae6dd6cbe7b53f2d21f83603cb56032c9b43b06c0596002d7a595206fcf3dac26d016549764a603f9ef36d9a7d8b814dca97e2c817150056c1d2a27026e0b8160bf848b7c5204b4261115bb6eb27880fcfe576037cdb0042f06d07bea0bab6c300dff6107c3b806f7b00df4e037c3b42f0ed043e4a970000be5d06f87686e0db057c94aed8209f60e9a6b074535884dddd7aed7af7b5005dace3e5b21bca05cb486c09d88fcc7b0c94d56e858fe27b808974dd0cb2240014965ce517c4d71df85a0df0ed09c1d70a7c94ee02e0db6b80af3504df5ee000a3741702df3e037c7b43f0ed033e4a7711f0ed37c0b72f04df7ee0a374170300df01037cfb43f01d003e4a7709f01d34c0772004df41e0a3749702df21037c000743f01d023e4a7719f01d36c0772804df61e0a3749703df11037c8743f01d00013e4a7705f01d35c0772404df51e0a3743d80ef9801bea321f88e011fa5eb00097cc70df01d0bc1771cf8285d2fe03b6180ef7808be13c047e97a03df4903007c2742f09d043e4a87ff2beed4cbe7f54f4f86e0bb1358eed6cbe2ad677e67000896bb81e52ebd2c2903fe25459ef768ce53e4712f6b7f7e88bd04f6df03e50075afe6f28a814dca97e2c8e758cf6f56c17252e14cc071272de023dd5dc0a200bb2d103e5f27f3a2bc3b71195194b1a9b9af5121fca77b5ea1cc9718c8561c008e599fc8708d915cc5b09fce15f661f0fce9ee6bc658dbbe521ae264ab187c00390a2cbafb9531d6b65f94b6ccae897e7e987eea11732c49ecfba21d13ff0d00c3fcb739649005ff2fa11dcdffd7bd76e97a9917e52daeff5945c67cf3da25003a8fd42e5daff81a87636641bb342fa05d32781d24d5ebaf14ec922dfccf79000c58343f1b489600470133767e521d7d1e42db788b584659c45265114bd22200963e16b10cb088e5728b582eb488a5ab452c718b58a659c432c12296d116b1009cb088a5da229694452c832c62b9d222962b2c62596f11cb4516b1242c62590064114b278b58a65bc432c622961a8b58ca2c62196c11cb5516b1f4b088e562008b58565ac4526c11cb7516b15c6b114b678b58c65ac432dc2296728b58865800c472b5452c3d2d62b9c42296128b58ba59c472bd452c8516b1ccb588e5068b00584c8e758765495bc432c22296fe16b15458c432d42296528b587a59c462f2003d8bb02cdd2d62a9b388a5c822969916b15c6311cb388b58465ac45269114b005f8b58fa59c432cc2296de16b15c6611cb0516b10cb488a58b452cb1b3cc920060ede72ae3bba4a5a0a379be387f95debd3d0cba82001b34ffe920e868ac9500f228e4f25c717b86024843ef22c703180e05b0ee0f486bbaccd14e1ae264ab001818f65bc0d2c522968116b15c6011cb6516b1f4b6886598452cfd2c62e96b00114ba5452c232d62196711cb3516b1ccb488a5c822963a8b58ba5bc472a945002cbd2c6229b58865a8452c1516b1f4b7886584452c698b588e58c47283452c00732d6229b488e57a8b58ba59c4526211cb2516b1f4b488e56a8b588658c452006e11cb708b58c65ac4d2d922966b2d62b9ce2296628b58565ac472b1452c3d002c62b9ca2296c116b19459c4526311cb188b58a65bc4d2c922964516b1242c0062b9c82296f516b15c6111cb9516b10cb2882565114bb5452c272c62196d1100cb048b58a659c412b788a5ab452c175ac472b9452c032c62e963114bd22296002a8b584659c432de2296823cb1e03764d2103f0476f7e9b5eb7d4f60afde3c00bdb5785b655e85325fe2265b713866acbc99d0bc11d28b8de6efee83f371480039ae88e9ff2697f0a11f6b7b3efa39bb67cdaee63aaae59b66fb80cfc437eb00f686e0db639005bf938676347fe7d05b379ce63050dee2d9d4ecae199b7abf0067906a14657a5ce6456d1531047dcfa006d60dbf4372e1bae1ad2cb31d031f00a8cd3a0e79d2fe45d0fee1b707e83fbcd0d17ae4834147734f8680eeb00c0f00051ddd4786818edad524e8e8fe96021dd5f132d0515d28071dd58b0ad0d13900a439bef87d4f7c8f4ff7775863aced7744d310df017cf4fd4bfc762885ab40004769aa4147df6bad011d7d637638e8e8bbb8234047dff01d093afaeef028d000d1778747838ebee93d0674f41df2b1a0a36fa6a74147df791f07ba26191e0f00ba75323c01746b65781ae8d6c8303dcb16e77795a213e77785c2923cb3cd3b00bf6487f2a5f80ae05b2dc3ab4047e199c0bc5cd109e64603cccb15668a3702001ff12f071d85e7027383a213ccf506981b14668ad7031ff137808ec28b80790099a213ccb506989729cc14af053ee25f063a0ad701f3524527989718605eaa0030537c09f011ff52d0517825302f56748279a101e6c50a33c517021ff12f06001d85d703f302452798e71b605ea030537c3ef011ff02d051f88461be5d0adf002e85cf94ddd58addd579b2bb42b1bb224f761b15bb8d79b25bafd8adcf93dd005ac56e6d9eec2e51ec2ec993dd858add8579b27bbed5e7b3d56e9c6ff5f96c00b51b67eb3a72f7c1fcd875f7c1fcd83ddfee831db97ee7e9b5eb7d877a3e6b00bb657b663a0f58eed05c0606fcf3c66ae668ce53e4311bca64be523625b07f000e94d76ccde515039b942fc5912f2aac09d01d031ded3f0aba59328c6be7dc002ec38741779b0ce33a39b7caf001d0dd22c3fb4147df7fdf07ba9b64782de80066c8f01ad0dd28c3ab4137558657816e8a0caf00dd64195e0eba4932dc08ba008932dc003afa0e703de8e87bcfcb4047df58ae051d7d0f7929e8e8bbd54b400047df6c5e0cbafb647821e8ee97e1f9a07b850cdf01ba57caf01cd0bd4a86f70082ee0119de03ba57cbf06ed0bd46867781eeb532bc1374af93e11da07b508600b783ee2119de06ba8765782be81e91e12da07b54863783ee31196e01dde33200dc0cba27647813e85e2fc31b41f7a40c6f00dd5332dc04baa765781de8de2000c30b405720c3f340179761bc56690ef42cd0d13b43b783ae50866f035d910c00df0a3a5a37e916d0d19ca99b4147738c6f021dbd07330374f47ee48da0a3f70037a7828ec6d0a7808ed62a9a0c3a9a3335097434c77822e8e8dd13fc4e37bd001f89df5ea77516ee021dad4b7437e8687ed43da0a339bcf7828ede33b90f7400f42ee4fda0eb25c3af001dad41f44ad0d1fca357818ee6eb3e003a7aa7e4d500a0a3f71e5f03ba52197e2de8facaf0eb404763d90f82aebf0c3f043a5a63e50061d0d17b628f808ee6743d0aba8132fc18e8e8bdaec74147ef1e3f01ba1b6400f8f5a01b24c34f828ec61d9f021d8d3b3e0d3a1a77a46b515c07fac76dfdf100f1e3aced96adaf47f6058bdeefa9fb63c8c780210d366e00bb87b5daf5fb21004714bbc441b64a58fb6f4be398702bf0ed37502ed4dfa0b17575bd84381c3300493686d486915e6c7d037c50e70625207c008e235b7b213fbaef16409afdb0007f37840f2a69700ec86eb0b34bb1a37d6c3be5f7d777023bd9dc016c647f2700706cd3c921eb1ef52b285faaebd8b7201d1dabcee7d03d8f4ed83cc2da6ed900da059c5b77502f8b370fee1030a4c1067eb743ef75e79f9b038a5de2205b2500504e78add0b57014f85a0d940b5d4bd42e1003d98ac331b54abb407ab11d0f00f0419dab9480f05e38ae55860f437e743d17409a56d88fcf35f6296944991e00558ec33925644794295d0f69f99b3c934db60b3b809d6c6e0736b2bf0338b600eae490758fae7fca97ea3afebf201d1d2bda8559459932a3f38afdcea38a4e001c7ec400ff51d6969fe278efa1fb2c5e277adbb2b2e499b465bafb1a06daea00a4fe36b7dc9b7a7500cae488523625b0ff2094d701cde5856d3fe54bf103c600ec367aedc0fe1cfeef0fe030710f3a95fffb8dd9adf0a629eecbe1ffbe008e007d79f47f9f39bbde63b1bd39fcdf1bc0a17b7e7536fff71ab35be93d3e6ccd00e17f6b00476b1efd6f3566b7ca9b8eba2787ff7b023874cf19cfe6bfb9b9ea00296f5af1ee1cfeef0ee0d03c973dabffbb8dd9adf0a62defcae1ffae008e5d0079f47f9731bb75def4f39d39fcdf19c0b1338ffeef3466b7ae4ee4b12387ff003b02384cccc13f95ff3b8cd96df0a6e86fcfe1fff6000eadffc772f8bfdd9800dd2aefd58d6d39fcdf16c061e2f9d0a9fcdf66cc6ebdd7ffd99ac3ffad011c0026fe079fcaffadc6ec56d78a3cb6e4f07f4b00c7963cfabfc598dd06affddb009cc3ffcd011c9bf3e8ff6663762bbdf3df92c3ff96008e963cfadf62ceae7700ff6fcee17f730047731efd6f36667799f7fc63530eff3705706ccaa3ff9b8c00d9adf3daff8d39fcdf18c0b1512b4776ff371ab35be3f57f36e4f07f4300c700863cfabfc198dd5aef95d4a61cfe37057034e5d17fe4eb28eb8eb3ccaadf6e00b9f7ac665d0effd70570accba3ffeb8cd9adf4faea6b73f8bf3680636d1efd00275bfaedd67bcf2a689ea1d882fca7fdc8b1462b4776ffd718b35be3ddab5600e7f07f7500c7ea3cfa8f7c1d656d3dcbacfaed5679fdea5539fc5f15c0b12a008ffeaf3266b7deeb57acc8e1ff8a000e13effe9fcaff15c6ecd679fdeae53900fc5f1ec0b13c8ffe2f3766b7cc3bff8d39fc6f0ce030b18ec2a9fc6f3466b700d61b576cc8e17f430047431efd6f00bbbae722e05a108532dfeb943288c331007de424e6ee0a1b961da5c7f56dd007cdeb5954e0da10aa0ff5e0031dd30f7c002802bd2e1e033e7a7d4a7a0f42cca93916e01fed1f589c39ee7a194ec03938000ef9d404eca72ddb9c142a33e1abe6753392b806059dcf2501764dae7d1193004236481f8770354de8676ddfc7a3f2c5353e96061c87e106254d09ec5f6ad800e753bdc348b6443d190c758aea8c81b94329f417cbe53a2817da8f73b44cac009153cbda968bdaa60896930a27ae8783f3d9961ae03bd57a384b818f748780008ffcc0f66444518675a856567fbe38ad5146d733bd1741b6e270cc54e51e43007ab1f595bf945e1c3a58392e01615c578d6c0d021dad933604d2962b3a512600155acbc43f7f6487f2a538aeb5466bb695e797afaca37c650a5f118435b178006555a6b004d9ad3660572d03aa43d586ed562a76538add049c07dab2ddaf71005dbe2acdbc22cf1abd797a7d005a6f8fda0cb291025f4668f605ed521f806c00903e0ee155d007189109bedcaee03a82d501c761b842495302fbab0dfb5c03001c6988932d718f5802f77dcd75de3bdfd50a03c553a0af0928af6a282fda3f000c74d4de97836e88924702f2c1765573bd4e65f313cf3be9708dcd9a00bee1007af9cab2d585e1c047ba2a60d1dd0e328585eef9d8065ea3949da8a73f916100ec1be03d7fa8528e229f5440d99669f627c6dad6e534c4711d56aabba9fcf2009575942fa9f099e81f2558db7bbed8b2dddf2a804577ffc3807f5e11e23dbe004cf1a304f697826f959a7d8bb1b6fd8834c4b1cf60a2ff4bf9d3fdbdbf5206007138e63ee53f4150d9517aec37571a2ebb246b5b76c900bb26ee216aff90da00b91ac376ab14bbc314bb61fba526ef1f06ee8f49ec0f52bd251bc3c097910600ca7e0494670c6c903e0ee167a05f3a32137cf9de876b59d7041c87e10a254d0009ecaf31ecf370e048439c6c89fbfdc3d02f35f13fe454fd217c2e313ca0bc00b03f47fb53a0a33e4925e882faa579e8f7a54eb7df9780fdc8a7f93f4a59b600ba3002f84887ff974cb4832a0bdd7bb01decaf949fa8ab5f07ae22bd5c15b800b60d6dd9dade22f82dd45c466229005a636659ed9a35335bead6ac5c36b96500ddb2e6954deb6240d7194a476c71202c8030eae9585ca5a733e8bac87021e8000a14af71c5a02ef0ab9698d66a2320c631bf4b47f1f12cd3a48bf804253e5100894f52d23fa2c41f53e24f28f12795f8d34afc1925fe4625feac127f8b127f009b127f87127f97127f8f127f9f127f4e897f40897f58897f4c897f42897f5a0089bfa8c4bfa0c4bfacc4bfa6c4bfa9c4bfa3c4bfafc47fa4c47faac47fa1c4007fadc47fa7c4ffaac4ffa9c4ffadc4ffa3c4ffa7c463313f4e9fa260b04f5c0030e2e213179bb8b8c4c5242e1a71b9d31257a219164b5889aeba58a24a2c49002596a012cd8158624a2c29259690124b468925a27a307f0928b1e49358e249002ce9249670124b365d2dd9fa327fc925d1a48abffc6209a501cc5f22490c6700886507c49224e2962abac2e25624ba3ae2162dbae4e2b62bfe2688bf86e2d6002a6eb9e216266e13a2c9164db4b88d886e85f854c768f0fd53f2579487f81400479af9d7f078e65fabe2fa14d7e464e62fcf3595f99fd398cefc65bd66307f001930b13c9858364c7c8e422c2926961a134b9089a5c9c49265626939f1d9870079cc5f7a4e2c4927963c13c370e2d30a62d8510ccd2d65fed04d1df38777c4007094187e12d31396337f9a8af8448098ae23a62c892966629a9d986a28a65b008aa5f8c51459314d584c9516d3c5c59479f1da80787542bc3e225ea111af110089e9a9e27532f14a9d78ad50bc5ad9cafc576cf731ff556bf1bab978e55e0c00311d66fe340031342786a8c550e809e60f858965c5c4326262d930b14c985800164c2c032696fd12cb7c8965bdc4325e0f307f992eb12c9758864b2cbb259600d912cb6a8965b41e61fe3259a29d7b9cf9ed9b58e64ab46b4f31bf3d13cb54008976ecff98df7ebd89f9edd69b99df5ebd95f9edd4db99df3ebd93f9edd2bb0099df1ebd97f9edd0fb99dffe3ccffc76e7835c3ec4fc36e7235c3ecafcf6e600e35c5e607e5bf349e6d719d1ce7c86cb6799dfc67c8ecbe799dfbe7c91cb970098dfb67c85cb5799dfae88eec13798dfa67c8bcbb799df9e7c97cbf798df9600fc80cb0f99df8efc98f98fbb441bf2332e3f677efbf14b2ebf627edbf11b2e00bf657ebbf17b2e7fe0f2472e7fe2f2672e7f617e1bf2372e7fe7f20fe6b72100ff627edbf112f3db8cff32bfad108d8168230ab8c4b974e2d2994b612cf3f7003cc632db1b64a4af8cd7363737ac5ddf5cdadc54bab6654df3caf56bb6956e0059d9bca2b46973c3c6c6354d5b30f16279dbee29e3e3366eacdd56ba725d7d00c3d6d2a696e6d2a6c6d2baa69675f59b30d1a18233b0785c26eed33e716d7d00fda9d33d28d3d12a7ad3b2333e723a8e3d793a89c6743d8d441313a79168e900e924ea5d7c1a89fa9e4ea229a793e8de8e2662ff0f44890a9f572d0200dbaf0010c3000000240b1f8b08000000000000ffed9d05781c3716c7b5dec44ed64e00ca6da8ad93b469dad0aed9c10d3649296d430d1bc371c00e3353b977d7837200afed518ff97acc773d666666861e48337addbfe5f1ae2791369a44f37def5b00e9cd48eff7341a8d76a4d114c6181bcefc2dcee54ad6718bc9dfb4fc4d9ede0096d29857d224672c229c0511e18c4784b35b4438bb4784b330229c4511e1ec001111ce9e1a39055b016bbfe9e64d182857dd8cc52c1ae7be24229cbd22c2d9003b229ce74584f3fc88705e1011ce0b23c2795144382f8e08e72511e1bc3422009c974584b34f4438fb4684b35f4438fb4784734044382f8f08e7151a39059b00fa9c5137ef9506ca55376369041807468071500418074780f1aa08305e1d0100c6211160bc26028c4323c0786d0418af8b00e3b008300e8f00e38808308e8c0000e3a808302623c0988a0063590418cb23c0581101c6ca0830564580b13a02008c351160ac8d00e3e808308e8900e3d808308ed3c87815cb6ce3e5ef04857700a2fc9d247f27cbdf29f277aafc9d267ded26e3d77399ce65069799721f15ca000d5c6ee47213979bb9dcc26516975bb9dcc6e5762eb3b9cce13297cb3c2ef300b9dcc1650197855c167159cc650997a55c9671a9e352cfa5814b2397262ecd005c967359c1652597555c567359c3652d97755c5ab8ace7b281cb462e9bb8b4007269e3b299cb162e5bb96ce3b25dfa522a7dd9c16527975d5c7673d9c3652f00977d5cf67339c0e52097435c0e7339c2e52897635c8e7339c1e524973bb9dc00c5e56e2ef770b997cb7d5ceee7f20097977079299797717990cbcbb9bc422900f3577279159787b83cac703ec2e5512e8f71799ccb135c9ee4f26a2e4f7179009acb335c5ec3e5b55c5ec7e5f55cdec0e5592e6fe4f2262e6fe6f2162e6fe500f2362e6fe7f20e2eefe4f22e2eefe6f21e2eefe5f23e2ecf71793f6b7ffe3f00c0e5835c3ec4e5c35c3ec2e5a35c3ec6e5e35c3ec1e5935c3ec5e5d35c3ec300e5b35c9ee7f2392e9fe7f2052e5fe4f2252e5fe6f2152e5fe5f2352e5fe7f2000d2edfe4f22d2edfe6f21d2edfe5f23d2edfe7f2032e3fe4f2232e3fe6f213002e3fe5f2332e3fe7f20b2ebfe4f22b2ebfe6f21b2ebfe5f23be90bcd75fa3d00973f28ba3f72f9930cff59fefe45fefe5539f66f5cfe2ec3ff90bfff94bfff0092bfff96bf2fc8dfffc8dfffcadfffc95f6a6c62f2b740fec6e56f37f9db5d00fe16f2df4f14fbe12296d9d2f237793a5b65b2babb67d4cf6b3c6bbf25a00c00e818a18b932ba0a3fa5d00baee5217075d2165073af2ab3be87a485d21e87a004a5d11e81252d70374b2b8584fa913f997485d5afe264f67ab48d6893c7be900cc53ce9deb2df32a017fce93ba5ea03b5fea7a83ee02a93b0f74174addf9a000bb48ea2e00ddc5527721e82e91ba8b4077a9d45d0cbacba4ee12d0f591ba4b0041d757ea2e035d3fa9eb03bafe52d7177403a4ae1fe82e97bafea0bb42ea0600808ec6c92e075da9d45d017564a0d4a5e56ff274b68a54b3c87390ce3ce53c00e0f7b38c1fe3257f01e826485d1c7469a9eb06ba8932d21d7493a4ae10749300a18c483745ea7a806eaad4f504dd342873ca6394d672aea816792675e6c97300157996e9cdd31bb32d6799f24d838d24944f855ebb29b41b934236481f87f0001896d92a204ced6a19c45301c761789492a604f6a70cfb5c061c6988932d510035b1dc35d7cb7afdf5b23c792ed7cb192cb3611da3fbf0d9542fc70383de7a00596da05e969dd3f5721ecb6c58c7a8df7636d5cb1b81416fbdacad77f7f1ac005be87ad9c4321bd631fa9f7036d5cb05c0a0b95e36bbf632eb16ba5e6e6299000deb18fd2f3d9beae50a60d05b2feb9b5dff32eb16ba5eee63990deb183d0700399beae56660d05b2f9b0cd4cb64d9b95c2fef66990deb183d773b9beae5410060d05b2f9b53ae5e66dd42d7cb875866c33a46cf79cfa67a799f0c8be7987f0096cf34fb83ee2f523700747f95bacb815f739dae72753aeb16ba4e3fcb321b00d64f1a57389beaf413322ceaea3f645d2d05dd3fa56e20e8fe25758340f76f00a91b0cba17a4ee2ad0fd47eaae06dd7fa56e08e8fe2775d7808e062986822e002675d782ae40eaae035d5cea8681ae9bd40d075d77a91b01ba42a91b2975e200990e8db97c4aea8a209fb4fc4d9edee68d05122f6d31259e86f070732cc9120060413b23f5da294f40d977c5e791c0a2b55de55b02f2ef0acb2860d1dc1e7b00fd9694de3cbd733a4229c704d81901fe68be1778ed538ab52f478a635b995200ca3601fb91af5c2f5f59b6f6b31cf882ee5fbaef9bc2e7c14ad988e6e7eb600053f375e8ddabe93aa7f5b188816cc5e1985dc519ae6f0397ee365170d17d8700f2a5b8c1f6ef5cb3ebd5b9ab143be2dcfe046ceaad73a966ac73a27fb807180082eadc54a873bf90ba62d83f0c74941eefebb6b473828fdab7547ef9cabaca009754f80cdc8f5238f790f27576f5df4f307f71edd0f54276e2b0ffd30599e3005e00a6617a99cac2f6fb86192b9fb264d87e7719b0e8ae2326cebf81fe925700a72a02ce4fd0ffde7228af33f1bfd7b13a56c7ea581dab6375ac8ed5b13a5600c7ea581dab6375ac8ed5b13a56c7ea58a3c48a63f938c72568acfc4cf191ce00e4d886f099e616e158f99db18c4dbdcfe8cbbcb1722a73316e590a0c642b0e00c73c12cb70dd2bc3386e896394eab8a581792edef9c3792e698893ad62e0c70039379ae71da4b02c29dfb3d76e598389f78ac4bbb0e2fd7baa4f49c50f9c4b00738da21375f2f9828cbfbac7b3c28ead191c7f6d37a70dede81d1bf3db076a008b44d196b24cfb60e0be91a280b05b29f312edd240d6b15d8ec331af9527a2003733779fe86cbcbb02caa152afdd24cef765b2fce9da205b0908d3b1225d1500945f29945f25941f1df31668d7e7c035a47bce06ce0d61c08a5b1ac2383ead00f79cfa63e5652158f03c6b1e834e19a8b34913f551e451c53a9e1fec2bd1fe004a28af2acde5a55e176988239f6375ac8ed5b13a56c7ea581dab6375ac8ed500b13a56c7ea581dab6375ac8ed5b1468515df2b27ce041c97b2802f0ff30abc0067f7b4e606e52dc62e26c3d885c9b1301a5319a2f81a8763f61564b8ae97e10062d6712e4167e74ff3b3fbace78f6c15832f38de61623cad5c61397bedfa6300e57aafc7cc5839d5a72ac50f3aa738564c3a1ce7c3b58070fe46b9a23b53e70003af9972d05118dfa7d55dc682af5ae645eb3ed1f837d98ac3314b6599d2380074b5661e13edbac8a346e6d58d75ac4f71d8df086d5ab30c07b55f627f5bc0007edab28db35299095f47ebf5d53b9ff47d003a9fa303ec8ed56b378576691d002fb241fa38845b0b32e53136137cb17c89595c07b501c761b842495302fb6b000dfb3c1a38d210275ba29eac843ad506f76fddf73ff417cbe56a2817da8f7d001713fda81ae028651ddb149c13499ce23aa23a8a6d72ad01be6ae04b43bc1600f84887f350c80f6c4f70fee2b93a3fe908dc1782e6f450da21c0aca9feb76300ae5298d5be02ce093aa9dccb4cf4473b9b5365f07f56bb7347e54f73aac856008275fccfa7f6054a59c7ff3fd8177800dab58f435f8bea25f6ff2b159da9760007fd4e439c6c615f0bfdaeecc46f5cc7496f5fc7e725aeee0a07d9c2fafab000728dd500731a9847415a3ae63148cb58fbf699e69e627fb44a394e7f5fa52c00b0af42edc368f0818e795ab95ec768e631d01ff3aa3bf605ca02fca3fdaf8700ebe959e85bd23928877c9e0bd84f5bb6be279599f075bc5e5fbdf349df3ea500f3393ec06e5aafdd14daa5be27e54dfa3884df57105c3654bec42cae83710100c761b846495302fbc719f679bcc2a49e4f514fde0c75ea39e87beaee53a1bf00582e43a05c687fd0b33e3a5ed467ba1ef05ea1fbbac4ff27942fc5b3fd8fc0007b9dfefe5ec7f7754a59c76770f86c10dfa3c167839ad7004be2fb08c3c0e600f3e66c7a6541df0ea37c7b290ce7a25d5bd632c6e76e05c037c200dff0107c0023808fd2c5814ff7fb49a7ba2e32fe07ee067cbadf213bd5b5924b205d77e000d3bd7e72026c76852f097c94ae10f84cbc17950cc197023e5c7b987e35bf7300923ad5774ef0fedb03f874dfd3c2be8712b486f38bdf9666fa9fd1e358405700f870dc14efc9c4a7fb3942d8e7cb382e82ffe94cf165bb6f197cce5186cf1500ba522ed8efc432125bd0f310d3fff9295f8a570313e97a196449282cb9ca2f0088af37f0d518e0ab0ec157037c94ee3ce0d3fd9f27ec7f6f7cb64ce9ce073e00ddff7112acfd78402e3efcdf43e92e003ebdcf5b7cbed121f8c6001fa5bb1000f8748fef2458fb6f6de7e21b0b7c94ee22e01b67806f6c08be71c047e92e06003ecdcf833cbe7121f8f0190aa5bb04f82618e01b1f826f02f051ba4b812f6d00806f4208be34f051bacb806fa201be7408be89c047fa3ec037c900dfc4107c0093808fd2f505bec906f82685e09b0c7c94ae1ff04d31c0373904df14e0a37400fd816faa01be2921f8a6021fa51b007cd30cf04d0dc1370df8281d7e9bee7a00037cd342f05d0f7c94ee0ae09b6e80effa107cd3818fd25d097c330cf04d0f00c13703f8281dfeaf9d69806f4608be99c047e9ae01be1bf4f279ffdf6686e000bb01586ed2cb529180fcbbc27213b0dca8972565c03f6f6cf366cd798a3c6e00611dcf0fb197c0fe9ba1bc6ed15c5e31b049f9521cf91cebb9cd2a58662a9c0009386ea6057ca4bbd1204b4261115bb6b62e880fcfe52c037cb784e09b057c004175ed56bd7cde7d6b5608be5b81e576ad2ce5deb8ecad21586e0796dbb4b200f8f72dbdfef9f798d9016549764a603f9ef3d99a7d8b814dca97e2c8e7581d00ab6375ac8ed5b13a56c7ea581dab6375ac8ed5b13a56c7ea581dab6375ac8e00d5b13a56c7ea581dab6375ac8ed5b13a56fdac826596c29980e36659c047ba00db0cb2241416b1659b2712c487e7728e01bed921f8e6005f505d9b6b806f4e0008beb9c03727806f9e01beb921f8e601dfdc00bef97af9bc3953f342f0cd070096055a59fcefe2cd0fc1b20058eed0cae2cf99d2eb9f3f676a614059929d1200d88fe77ca166df626093f2a538f23956c7ea581dab6375ac8ed5b13a56c7ea00581dab6375ac8ed5b13a56c7ea581d6b545805cb3c853301c7cdb3808f747700186449282c62cbf69c3d880fcfe522037c0b43f02d02bea0bab6d800dfa210007c8b816f5100df12bd7cde98cee2107c4b8065995e166ffd962521589601cb0052bd2c2903fe79633a75ac6359929d12d88fe7bc4eb36f31b049f9521cf91c00ebb9cd2a58162b9c09386eb1057ca45b6a9025a1b0882d5bbb14c487e7b2de00005f5d08be7ae00baa6b0d7af9bc7b4c7d08be066069d2cae2cf1b6808c1d200042c8d5a59fc7b8c5efffc7b4c734059929d12d88fe7bc59b36f31b049f95200bcd99c5dcfffe539fc5f1ec0b13c8ffe235f57596f8e10ab2b5757aeae5c5d00b9ba7275e5eacad595ab2b5757aeae5c5db9ba7275e5eacad595ab2b5757ae00ae5cbbce2a58ea15ce041c576f011fe91a0db2241416b1657b761fc487f56e008501bee521f856005fd075b1d200df8a107c2b818fd2e1b5b04a2f9f374eb4003204df2a6059a395c55f937f55089635c0b25a2b8b3f4ea4d73f7f9c646d400059929d12d88f7572ad66df626093f2a538f29d8daccd11627575c00cabab03008ed5d501c7eaea80637575c0b1ba3ae0585d1d70acae0e385657071cabab03008ed5d501c7eaea80637575c0b1ba3ae0585d1d70ac67ba0e0896950a67028e005b69011fe9561b6449282c62cb364f24880febdd3a037c6b43f0ad03bea0eb00a2c500dfba107c2dc0b72e806fbd01be96107ceb818fd2e1b5ba412f9f3767006a7d08be0dc0b2c900cb86102c9b8065a35e969401ffbc3953ad016549764a00603fd6c956cdbec5c026e54b71e48b0aab6059af7026e0b8f516f0916ea341009684c222b66cd74f101f9ecb36037cad21f8da802fa8ae6dd6cbe7b53f6d2100f83603cb56032c9b43b06c05962d7a595206fcf3dac26d016549764a603f9e00f36d9a7d8b814dca97e2c8171556c1d2a67026e0b8360bf848b7c5204b426100115bb6eb27880fcfe576037cdb42f06d07bea0bab6c300dff6107c3b806f7b0000df4e037c3b42f0ed043e4a9700be5d06f87686e0db057c94aed8209f60e900a5b0f4525884dddd7aed7af7b55dacebe5b21bca05cb486c09d88fcc7b0c9400d56e858fe27b808974bd0cb22414965ce517c4d71bf8f61ae0db13826f2ff00051baf3806f9f01bebd21f8f6011fa53b1ff8f61be0db17826f3ff051ba0b8000ef8001befd21f80e001fa5bb10f80e1ae03b1082ef20f051ba8b80ef9001be008321f80e011fa5bb18f80e1be03b1482ef30f051ba4b80ef8801bec321f88e00001fa5bb14f88e1ae03b1282ef28f051bacb80ef9801bea321f88e011fa5eb00037cc70df01d0bc1771cf8285d5fe03b6180ef7808be13c047e9fa01df4903007c2742f09d043e4ad71ff8ee34c0773204df9dc047e9f07fc55d7af9bcfee9009d21f8ee02967bf4b278eb99df1582e51e60b95b2f4bca807f4991e7bd9af3001479dcc73a9e1f622f81fdf74279dda7b9bc626093f2a538f239d6739b55b000dca97026e0b83b2de023ddddc0a2bb2d103e0f957951deddb88c2ecad8d4dc00d7a810fed33daf50e64b0c642b0ec7ac4f64b8c64bae62d84fe70afb3078fe0074f73563ac7d5f290d71b2550cbe1c0316ddfdca186bdf2f4a5b66d7443f3f004c3ff5a8399624f67dd18e89ff8661fedb1c36c882ff97d08ee6ffeb5ebb7400adcc8bf216d7ffec2263be79ed129d476a97ae557c8dc331b3a15d9a1fd02e0019bc0e92eaf5570a76c916fee73c0e2c9a9f0d244b80a380193b3fa9ae3e0f00a16da6452ce32d62a9b188a5cc2296e116b10cb088e5528b588658c472be45002c2b2d62e969114bdc2296a116b14cb088a5d62296728b584658c472b9452c009759c47281452c098b58aeb588a59b452cf32c62b9ce221693cf4fc2b2a42d0062196d114b85452c232d62b9c222963e16b15c68114bb1452cf516b174b78800e56a8b586659c432d122963116b1545ac432ca22962b2d62e96b11cb4516b1009458c4d2cb2296428b582659c432d62296c116b15459c492b488a5d422967e0016b1989cc31f96a5b7452c4516b1ccb088e52a8b58265bc432ce22969316b100545bc432d022964116b1a42c621966114b7f8b582eb18865bd452ce759c472008d452c3d2c62596c114bec0cb32458c7b9caf82e6929e8689e2fce5fa5776f008f80ae20c006cdf339043a1af7a53c0ab93c57dc91a100d2d0bbc8f10086c30001ac0702d29a2e73b4938638d92a06860316b02cb688a587452cd758c4729e00452ceb2d62b9c42296fe16b10cb388256511cb208b58065ac4526d11cb498b0058c659c432d92296ab2c629961114b91452cbd2d62b9d822967e16b1945ac40092b488a5ca2296c116b18cb5886592452c8516b1f4b288a5c422968b2c62e9006b11cb9516b18cb288a5d222963116b14cb4886596452c575bc4d2dd22967a008b588a2d62b9d022963e16b15c6111cb488b582a2c62196d114bda2296a31600b15c6711cb3c8b58ba59c472ad452c098b582eb088e5328b582eb788658445002ce516b1d45ac432c12296a116b1c42d62e96911cb4a8b58ceb7886588452c00975ac432c02296e116b19459c4526311cb788b58665ac452902716fc864c1a00e287c1ee7ebd76bdef09ecd39ba7b716ef5e9957a1cc97b8c9561c8e99206f00b2342781f462a3f9bbfbe17c1c568e2b62fabfc9257c18c4da9f8f41ceee1900b3abb98e6af9a6d97ee033f1cdba7d21f8f61864c1efa4a11dcddf39f4d60d00a7e76294b7781e34a767c6a6deef19a49a45999e9079515b450c41df33a8810075c3ef905cb86ef85e96d98e830fd4669d803c69ff6268fff0db03c358267f005a8f7c38e8e879e608d01d91e191a0a3fbc828d051bb9a041ddddf52a0a33a005e063aaa0be5a0a37a51013a3a8734568bdff7c4f55f747f8735c6da7f4734000df11dc047dfbfc46f8752b80a7494a61a74f4bdd61ad0d137666b4147dfc5001d0d3afa86ef18d0d17787c7828ebe3b3c0e74f44deff1a0a3ef904f001d7d00333d0d3afacefb44d0b5c8f024d0ad93e1c9a05b2bc33340b74686a9af26ce00ef2a4527ceef0a8525797a9b777ec90ee54bf115c0b75a8657818ec2b3807900b9a213cccd0698972bcc146f063ee25f0e3a0acf03e6264527981b0d30372900cc146f043ee26f021d8517037383a213cc7506981b14668ad7011ff137808e00c2f5c0bc4cd109e6a506989729cc145f0a7cc4bf0c74145e09cc4b149d605e0064807989c24cf145c047fc4b4047e1f5c0bc50d109e6050698172acc145f00007cc4bf1074143e69986f97c2b74be1336577b56277759eecae50ecaec893dd0066c56e739eec362a761bf364b74eb15b9727bb4b15bb4bf36477916277519e00ec9e6bf5f94cb51be75a7d3e53edc699ba8edc7d303f76dd7d303f76cfb5fb006057aedff97aed7adfa15ec0da6fd99e99ce07963b34978101ffbcb19ab99a00f31479cc813259a0944d09ec9f0be535477379c5c026e54b71e48b0a6b027400c74147fb8f816eb60ce3da39b7cbf011d0dd26c3b84ecead327c1074b7c8f00001d0d1f7dff783ee26195e0bba1b65780de86e90e1d5a09b2ec3ab4077bd0c00af00dd34195e0ebaa932dc0cba2932dc043afa0e7023e8e87bcf0da0a36f2c00d7818ebe87bc0c74f4ddeaa5a0a36f362f01ddfd32bc08740fc8f002d0bd440086ef00dd4b65782ee85e26c3fb40f7a00cef01ddcb657837e85e21c3bb40f7004a19de09ba57c9f00ed03d24c3db41f7b00c6f03dd2332bc15748fcaf016d0003d26c39b41f7b80cb781ee09196e05dd9332bc0974af96e18da07b4a86378000ee69196e01dd3332bc0e74af91e185a02b90e1f9a08bcb305eab34ef7836e800e83d9ddb415728c3b781ae48866f051dad55740be8682ed9cda0a379bd3781008ede83b91174f4aee60da0a37749a7838ec6d0af071dad0f340d74347f6b2a00e8685eef14d0d17b30f89d6e7a5713bfbd4eeff0df0d3a5a7fe71ed0d1fca8007b41477378ef031dbdf3723fe8e8bdcc0740d74f865f023a5a6be7a5a0a3b900502f031dcdd77d1074f47ecbcb4147ef60be0274a532fc4ad00d94e157818e00c6b21f02dd60197e1874b466c623a0a377d61e051dcd757b0c74d7c8f0e3a000a3f99c4f808ee6b33f09baeb64f8d5a01b26c34f818ec61d9f061d8d3b3e03003a1a77a46b515c07fac76dfdf1f113acfd96adaf47f6058bdeefa9fb63c8c70081210d36ae03bb47b4daf5fb214715bbc441b64a58c76f4be398f05ee03b6000a05ca8bf4163ebea3b38713866aa6c0ca90d23bdd80606f8a0ce0d4a40f820001c47b6f6417e74df2d80340760ff6e081f52d2e01c90dd6067976247fbd87600caefafef0476b2b903d8c8fe4ee0d8a69343d63dea5750be54d7b16f413a3a00569dcfa17b1e9db07994b5dfb2b50b38b7ee905e166f1edc616048830dfcc600a2deebce3f370715bbc441b64aa09cf05aa16be118f0ed35502e742d51bb40000c642b0ec7d429ed02e9c57622c00775ae5202c2fbe0b8bd327c04f2a3ebb90000d2ec85fdf85c63bf924694e931e5389c5342764499d2f59096bfc9d3d96400bbb003d8c9e6766023fb3b8063ab4e0e59f7e8faa77ca9aee3ff0bd2d1b1a2005d985d9429333aafd8ef3ca6e8c4e1470df01f63edf9298ef71ebacfe275a200b72d2b4b9e4e5ba6bbaf61a0ad4eea6f736bbdc70707a14c8e2a655302fb0f0041791dd45c5ed8f653be143f68ceae374def400eff0f047098b80775e63ff2007595f5d01966d56fb7d99b26b83f87fffb0338f6e7d1fffdc6ec567ad347f700e5f07f5f0087eeb9e0d9fcdf67cc6ead77fef7e6f07f6f00c7de3cfabfd79800ddaa7a91c79e1cfeef09e0d03dbf3d9bffe6e6d5377953a077e7f07f77008700e679f759fddf6dcc6ebde7ffae1cfeef0ae0d89547ff7719b35be74d95df9900c3ff9d011c3bf3e8ff4e6376ebbce9f43b72f8bf2380c3c4fb029df9bfc39800dd4aafafba3d87ffdb0338b4fe77cce1ff7663766bbdd74cb6e5f07f5b0087008967599df9bfcd98dd06effeb73587ff5b03384cfc67efccffadc6ec367bed00ff961cfe6f09e0d89247ffb718b35be9f9bf3987ff9b033836e7d1ffcdc6ec005679ed7f5b0effdb0238daf2e87f9b31bb8dde2b66ad39fc6f0de068cda3ff00adc6ec367bd33a36e5f07f5300c7a63cfabfc998dd2aef59cdc61cfe6f0ce000d8a89523bbff1b8dd92df7feff6fc8e1ff86008e0d79f47f8331bb7575228f00961cfeb70470b4e4d1ff167376bdfbdfba1cfeaf0be0589747ffd719b35be700d5ffb539fc5f1bc0b1368ffe932dfd76cbbd57a7699ea1d882fca7fdc8b146002b4776ffd798b3ebf5ff57e7f07f7500c7ea3cfabfda98dd4a6fcc72550eff00570570accaa3ffab8cd96df4dabf1539fc5f11c061e27dfacefc5f61cc6ead00e7fff21cfe2f0fe0589e47ff971bb3dbe0b57fcd39fc6f0ee030b1364167fe00371bb35be63dff6acae17f530047531efd6f3266b7c2abff8d39fc6f0ce03000b1ce4367fe37825dddf31bf03db04299ef50a50ce270cce57262746f850dcb008ed2e39a39e84383561ffcb94c759df8d0003ed03183c18722a67dfd8b0a03003e7acf6997c9bcc43c9de301fed1fea1c599e3ae93e1049c8313904f6dc07e00dab2cd73a13213be2ed1eb6b12df99a3f3b924c0ee42bd76dbbdab1793423600481f87700dbd24c0daaf6941e58beb6d2c0d380ec38d4a9a12d8bfd4b0cf9d00ad2142b6443d1901758aea8c81f94829f417cb6528940bedc7795fbaafb7a000f560d43645b004bd1f457514e7c89958afa64ee1a3f852e023dd61e0233f9600c95f714e47176558476a65f5e7a0d3ba67743dd3bb16642b0ec7cc50ee31a40017db40f94be9715ddb51e0238571ad36b2350c74b4f6da08485baee844995400682d13fffc911dca97e2b87e1bad03579e5fbeb2aef295297c4510d6c4e295005599c21264b7da805db50ca80e551bb65ba9d84d297613701e68cb76bfc6b500feaa34f38a3c6bf4e6e9f501680d3f6a33c8460a7c19add917b44b7d00b24100fa388457431f607426f862bb826b1356071c87e10a254d09ecaf36ec730d7000a4214eb6c43d6219dcf735d779ef7c572b0c144f81be26a0bcaaa1bc68ff2800d0517b5f0eba114a1e09c807db55cdf53a95cd4f3cefa4c3753b6b02f86af500f29565ab0bb5c047ba2a60d1dd0e328585eef9d8065ea5949da8a73f9161ec001be03d7fa4528e229f5440d99669f627c6dad7e534c4716d57aabba9fcf2950075952fa9f099e81f2558fb7bbed8b2dddf2a804577ffc3807f5e11e23dbe4c00f1a304f697826f959a7d8bb1f6fd8834c4b1cf60a2ff4bf9d3fd7db05206710038e601e53f4150d9517aec37571a2ebb246b5f76c900bb26ee216aff90dab9001ac376ab14bba314bb61fba526ef1f06ee8f49ec0f52bd251ba3c0973106ca007e3494670c6c903e0ee1d741bf744c26f8e2bd0fd7c7ae09380ec3154a9a1200d85f63d8e75ae048439c6c89fbfda3d02f35f13fa4b3fe103e97a80d282fec00cfd1fe14e8a84f5209baa07e691efa7da953edf725603ff269fe8f5296ad2e008c063ed2e1ff2513eda0ca42f71e6c07072be527eaead781ab482f5705ae9700435bb6b6b7087e0b359791585e80d6ad69a85bb366565bfd9a950dd3dad63500b4ae6c591703baee503a628b03610184514fc7e2ca3fdd41d743860b4157a000788dab10f5805fb5c4b4561b013191f95d3a8a4f6299265dc4272bf1294a7c00aa92fe5125feb8127f52893fa5c49f51e2af55e2af57e2cf2af13729f1b72800f1b729f17728f17729f1f728f1f729f1f72bf10f29f18f2af14f28f14f2bf100e795f81794f89795f8d794f83795f87794f8f795f88f94f84f95f82f94f8af0095f8ef94f85f95f83f95f8bf95f87f94f8ff94782ce6c7e9f3160cf6890b46005c7ce262131797b898c445232e775a364b34c362592cd15517cb5e8965aec400b256a23910cb568965aac4b25462192ab1ec541fe62f2b25969112cb46896500a2c4b2506219a82b25db40e62fe3249a54f1975f2ccb3484f9cb2e89e10cb100948158e644dc52455758dc8a445747dca245975cdc76c5df04f1d750dc5ac5002d57dcc2c46d4234d9a28916b711d1ad109fff1807be7f4afe8af2109ff7480033ff1a9ec4fc6b555c9fe29a9cc6fc25bfa633ff131d3399bf54d88dcc5f5a004c2c39269622139fb810cb9489e5cbc4b26662b933b10c9a58ae4e7c4a623e00f397b313cbdc89213831dc283ed72086e5c4d0ce32e60febd4337f284a0c3d0089a909627ac672e64fd3119f1d10d395c4942d316d4d4cdd13d317c5144eb100bcbf98ca2aa6f38a29cd625ab798da2ea6f78b571cc46b1ee25517f1ba8f7800e549bcf6255e7d13afff895720f732ff55d8fdcc7f255abcc22e5e0d17434c0047983f0d400ccd89216a31147a92f9436162a932b13499588a4c2c3d26961a00134b8b89a5c4c4d26162a930b134d883cc5ffa4b2cf52596f6124b7989a5bb00c4525d6269ae4799bff49668e79e607efb2696ce12eddad3cc6fcfc4d257a2001d7b1df3dbaf3730bfdd7a23f3dbab3733bf9d7a2bf3dba7b733bf5d7a27f300dba37733bf1d7a2ff3db9fe798dfee7c80cb0799dfe67c98cb4798dfde7c8c00cbc799dfd67c92f97546b4339fe1f259e6b7319fe3f279e6b72f5fe4f225e600b72d5fe1f255e6b72ba27bf00de6b729dfe2f26de6b727dfe5f23de6b7253f00e0f243e6b7233f66fee32ed186fc8ccbcf99df7efc92cbaf98df76fc86cb6f0099df6efc9ecb1fb8fc91cb9fb8fc99cb5f98df86fc8dcbdfb9fc83f96dc8bf0098df76bcc0fc36e3bfcc6f2b446320da88022e712eddb874e75218cbfc3d8f00b1ccf61a191928e375adad4d6bd7b796b6b694ae6d5bd3ba72fd9a6da55b5600b6ae286dd9dcb4b1794dcb164cbc44deb6fbcaf8c48d1bebb695ae5cd7d8b400b5b4a5adb5b4a5b9b4bea56d5de3264c74b8e0342c9e908907744c5cd7d8d80079ba87643a5a996f4676c6474fc5b1a74e25d1f89ea790684ae214122d3b950044038a4f21d1a0534934fd5412dddfd544ecff937dd038ab2d02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap b/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap index 3062c58963d..885bdff3d12 100644 --- a/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap +++ b/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap @@ -790,41 +790,41 @@ Fr { exports[`abis hashes function args 1`] = ` Fr { - "asBigInt": 11839099223661714814196842233383119055519657007373713796026764119292399532830n, + "asBigInt": 13773950327711008256617416059663646210697922258755635023101062905870427579114n, "asBuffer": { "data": [ - 26, - 44, - 177, - 84, - 151, - 13, - 83, - 84, - 26, - 98, - 206, - 96, - 113, - 195, - 152, - 109, - 8, - 146, - 63, - 234, - 71, - 75, - 232, - 160, - 170, - 26, + 30, + 115, + 199, + 148, 191, - 135, - 24, + 130, + 160, + 100, + 98, + 205, + 48, + 10, + 124, + 139, + 218, 39, - 59, + 47, 30, + 253, + 79, + 200, + 107, + 56, + 53, + 41, + 130, + 26, + 237, + 106, + 243, + 98, + 234, ], "type": "Buffer", }, @@ -833,41 +833,41 @@ Fr { exports[`abis hashes many function args 1`] = ` Fr { - "asBigInt": 9368119665570837995905174888524883816390941475336228173888734493993721486827n, + "asBigInt": 5019561503322397537490243039227402098195702132635946562396386724242519444026n, "asBuffer": { "data": [ - 20, - 182, - 42, - 246, - 214, - 208, - 1, - 110, - 254, - 196, - 157, - 194, - 3, - 246, - 106, - 69, - 102, - 180, - 241, - 249, - 168, - 116, - 85, - 53, + 11, + 24, + 248, + 156, + 4, + 206, + 67, + 253, + 74, + 84, + 242, + 151, + 150, + 30, + 97, + 225, + 13, 209, - 138, - 127, - 164, - 10, - 109, - 93, - 235, + 17, + 48, + 60, + 254, + 13, + 43, + 30, + 121, + 227, + 133, + 97, + 87, + 74, + 58, ], "type": "Buffer", }, diff --git a/yarn-project/circuits.js/src/abis/abis.test.ts b/yarn-project/circuits.js/src/abis/abis.test.ts index ad6a2070b3f..13680fb7315 100644 --- a/yarn-project/circuits.js/src/abis/abis.test.ts +++ b/yarn-project/circuits.js/src/abis/abis.test.ts @@ -127,7 +127,6 @@ describe('abis', () => { }); it('hashes function args', () => { - // const args = Array.from({ length: 8 }).map((_, i) => new Fr(i)); const args = times(8, i => new Fr(i)); const res = computeVarArgsHash(args); expect(res).toMatchSnapshot(); diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index 9c1b9c35b81..ddd9c6150af 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -7,7 +7,13 @@ import { boolToBuffer, numToUInt8, numToUInt16BE, numToUInt32BE } from '@aztec/f import { Buffer } from 'buffer'; import chunk from 'lodash.chunk'; -import { FUNCTION_SELECTOR_NUM_BYTES, FUNCTION_TREE_HEIGHT, GeneratorIndex } from '../constants.gen.js'; +import { + ARGS_HASH_CHUNK_COUNT, + ARGS_HASH_CHUNK_LENGTH, + FUNCTION_SELECTOR_NUM_BYTES, + FUNCTION_TREE_HEIGHT, + GeneratorIndex, +} from '../constants.gen.js'; import { MerkleTreeCalculator } from '../merkle/merkle_tree_calculator.js'; import { ContractDeploymentData, @@ -224,9 +230,6 @@ export function computePublicDataTreeLeafSlot(contractAddress: AztecAddress, sto ); } -const ARGS_HASH_CHUNK_SIZE = 32; -const ARGS_HASH_CHUNK_COUNT = 16; - /** * Computes the hash of a list of arguments. * @param args - Arguments to hash. @@ -236,13 +239,13 @@ export function computeVarArgsHash(args: Fr[]) { if (args.length === 0) { return Fr.ZERO; } - if (args.length > ARGS_HASH_CHUNK_SIZE * ARGS_HASH_CHUNK_COUNT) { - throw new Error(`Cannot hash more than ${ARGS_HASH_CHUNK_SIZE * ARGS_HASH_CHUNK_COUNT} arguments`); + if (args.length > ARGS_HASH_CHUNK_LENGTH * ARGS_HASH_CHUNK_COUNT) { + throw new Error(`Cannot hash more than ${ARGS_HASH_CHUNK_LENGTH * ARGS_HASH_CHUNK_COUNT} arguments`); } - let chunksHashes = chunk(args, ARGS_HASH_CHUNK_SIZE).map(c => { - if (c.length < ARGS_HASH_CHUNK_SIZE) { - c = padArrayEnd(c, Fr.ZERO, ARGS_HASH_CHUNK_SIZE); + let chunksHashes = chunk(args, ARGS_HASH_CHUNK_LENGTH).map(c => { + if (c.length < ARGS_HASH_CHUNK_LENGTH) { + c = padArrayEnd(c, Fr.ZERO, ARGS_HASH_CHUNK_LENGTH); } return Fr.fromBuffer( pedersenHash( diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 6aeeebb163b..bea47e9dd04 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -48,7 +48,9 @@ export const FUNCTION_SELECTOR_NUM_BYTES = 4; export const MAPPING_SLOT_PEDERSEN_SEPARATOR = 4; export const NUM_FIELDS_PER_SHA256 = 2; export const ARGS_HASH_CHUNK_LENGTH = 32; -export const ARGS_HASH_CHUNK_COUNT = 16; +export const ARGS_HASH_CHUNK_COUNT = 32; +export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000; +export const CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = 0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8n; export const L1_TO_L2_MESSAGE_LENGTH = 8; export const L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25; export const MAX_NOTE_FIELDS_LENGTH = 20; diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap index 1e861585d9d..122042ba049 100644 --- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap +++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_address.test.ts.snap @@ -88,41 +88,41 @@ AztecAddress { exports[`ContractAddress computeInitializationHash 1`] = ` Fr { - "asBigInt": 6008702290320255259549389675568071185910851926477784271985492188905918575237n, + "asBigInt": 11287307308183188516369033835241775664908452274022428157981236923904607656063n, "asBuffer": { "data": [ - 13, - 72, - 206, - 18, - 237, - 214, - 138, - 47, - 96, - 228, - 192, - 127, - 222, - 19, + 24, + 244, + 99, + 184, + 236, + 16, + 42, + 8, 156, - 23, - 220, + 101, + 39, + 106, + 125, + 199, + 236, + 87, + 43, + 9, + 28, + 163, + 148, + 181, 224, - 89, - 169, - 234, - 46, - 7, - 2, - 131, - 242, - 115, - 20, - 86, - 206, - 50, - 133, + 108, + 3, + 191, + 211, + 120, + 203, + 68, + 24, + 127, ], "type": "Buffer", }, diff --git a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap index 90d5d217ac8..9f9e8537ee8 100644 --- a/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap +++ b/yarn-project/circuits.js/src/contract/__snapshots__/contract_class.test.ts.snap @@ -20,6 +20,7 @@ exports[`ContractClass creates a contract class from a contract compilation arti "isInternal": false } ], + "packedBytecode": "0x000000028df71de50000002a171f8b08000000000000ffed9d097c1d55f5c7dfcb4bd2bcbcecfbd2242f4dd3360d69f3d28d5ad4b8518a0b1444aaa2a55bb0d8a6d8262c0a8a8ab8af7545140515dc57dc1744511064535054dc4041f6b52c65f99f3bef1ef2cbcdf425f3e7de97137ae7f339c99d7367eef99e73efdcb96fe6ceccb5b1582c1ecb2e0992fad8c485f307f5fffea7b7642c96d5ef92333e43380b66086762867016ce10cea219c2593c433867cd10ce9219c2999c219ca5338433354338cb660867f90ce1ac98219c953384b36a867056cf10ce9a19c2593b4338eb2c72b60027ffb66bd0ff1bf5ff26fdbf59ffe77d5af5ffd9fa7f9bf6b550afb7937490a4493a751e07660e4917c95c926e927924f3491690f4902c24e9253980a48f6411c962bdbffa813840b2846429c93292e5242b480e245949f22c92552407913c9be43924cfd5717b1ec9f3495e40f2429217911c4cb29ae4109235248792bc98e425242f257919c961248793acd5bea4b52f47901c49f27292a3485e417234c93a925792bc8ae4d524c790bc86e4b524eb498e25d940b2916413c966922d244324c791bc8e642bc9f124af27d946b29d64986407c909246f3062be936417c908c9a8c17922c9492427939c42f2469237919c4a721ac99b49de42723ac95b49de46f276923348de417226c93b49de45f26e92f790bc97e47d24ef27f900c907493e44f261928f90ec26f928c9c762e3ebffe3249f20f924c9a748ce22f934c9d9249f21f92cc939249f23f93cc9b924e7917c81e48b245f22399fe402922f937c85e4ab245f23f93ac93748be49f22d926f937c87e4bb24df23b990e4fb243f20f921c98f487e4cf213929f92fc8ce4e724bf20b988e497241793fc8ae4d72497685f0ab42fbf21f9ada1bb94e4329dfe9dfe7fb9fe7f85feff7bfdff4afdff2afdff6afdff1afdff5ac55f9d4dab31b3790d46e9f8988d838e8fdf02d0f1b19c001d1fd785a0e363bc08747cbc17838e8ffd59a06bd3e912d0b5439aff77e87429e8d23a9d025da74e97816e8e4e9783ae4ba72b403757a72b41d7add355a09ba7d3d5a09bafd335a05ba0d3b5a0ebd1e93afd9fe3a39641fdbfff692eaa4ccbe7917ec5ce6da31efce1b6d1003a6e1b8da0e3b6d1043af6bd1974dc365a40c76da31574dc3666838edb461be8b86d609be2b6d1013a6e1b69d071dbe8041db78d39a0e3b6d1053a6e1b7341c76da31b74dc36e6818edbc67cd071cc17808e63ce6d4ac5f810c8e7058f69bc3ecb3acec7633a0165b28ef3f198e67c3ca6391f8f69cce7ff9c8fc734e7e331cdf978fc723e1eab5caf785cf23e780c72bd623be672b0cd72bd62fbe4b2b12d72bd625b647bd816b95eb12d3203b645aed734e8980bdb221f4bd8169915fbb762286b50ffef7f7a4b06fb645ee2c6fa20a4d3fa7f31f86089a53f052c9d60a7cbae9d25782e998acf5dc0d26dd9673c5f4d85a51b58e6d96509aec9ceb75b6650a71c6bec43d9ce1cf06781657fe26087cbe575b695021df6e70b42f87aecf20dc4c10e97cbeb3dc0c7ba79ee62d58f7d1397adce131bc066dab24de53f1fe77cdf8219d85602b639b27a8c6b8bd695423e8e2d797fec53bb0d9d8b63280e76b85c5e9f077cdca774e7976f60aa7c730d3e077d5f10abb9064b985dcbc75d681d711bea716cd7ec8fba0cbbf8bb84975ce783f9c06bbbff54652eb45b6670ccf702f320d8c0f3ec010e62df0bf18c830dd627207d466c6c3900d269fd9f99d5b1d213b21da6e719fba420bfc7b1cf0b816310d6d996ea4b4f0106cb6d3ea8ef1e8381d7bb40bf30245e780ee4fc06d0717fdf0dba394619f85b1dfb55cbed3a93cb4fac77d6e16fc285217cbd76f90672b5855ee063dd02c7b1dad7b867e17e6cd7f6b187e5abe39c8f13b69380fc8e82b1edce0626cbbfbd06a2fedec17342da2a4ba63feaefc034b058feed1b9c6b3bec96d9efba4dc5207ecc8e7d089edb5c1c531d463df13af27956cfea593dab67f5ac9ed5b37ad6fd9b55b1cc3538f11ed85c017c61f7c06cff36c0fb8e5cb6faed7727d89c63d56626b81664fe9e33ef7d26609b2781eb5e9d2e857cbc9fd469e8ecff5eccd61fdbe172793d0d7cec0bd69feddf8bf1d8f8dfae83cf68bb039bec1f03997e35cf00e71099c75d5affc7b951ac536d724ec198bf768f95ec7d79bc3eaa965cd726e6388b7df61e6e97111bfbf531d08ff3160b62e3fb870eb06be99a468613786d94e3c71c61d7ca4a754594dbe5798a6b2af7855ddd9bc16bd8e9d8d83543b685d7ab795bf39e4e3a36f13e09de5ba9d1f153c7d085f1b1fd2cb7db81a8d7f7b06ddbadd3817e9ce7381516ac67cbe77e27f7f51cdcb308ae35e6baf786f7d1b0fd4dc77d34cfea593dab67f5ac9ed5b37a56cfea593dab67f5ac9ed5b37a56cfea593dab679d49ac389f3eadffef6bbefa74f1b10eef0fd9beb68dcfe372d9eadec57970ef226dd5e6c0b867e3b8fc66a32ef0d9b81be19ecaf93a5d0af961731db0fe5cdcc3dad75c07b6550abe60fdd9bedf81f715b9dc67aeddecbd72bbc7c0c053f7cab93d99c71d3eab633ed386f7f9707e04cedf481bbae9aa0f3c66d2a0e334ce5fb11be38c8b7b68c17b750f807a60dff09937cebf08fa908be3637563f6172aff9a907c5ee2c6fa20a4f13efb22bbbe067de662287f106ca0dd7ebb763368371e1bffce23d627207d3504085f1ac6f16566d5eefa42b6c37487b14f0af2fb1cfbbc083806619d6da9767209b4a96be07c69fb7c83fe625c9a202e9c9f86b8d83ede948be6f88e19f039bbb906278eabb00fec73c0b7af71551ff099733c70be20f627385f306d9575fc18c89c0f9406bb96ce13e3e603e1fc8bced8c4770c24609b9b74bb56f381cc714f1af66d8632f33187c93c37e31ca65b81d9cf610a9fc364cecdc5394cf740bfd65a904def6bbc2de5f9631cc7a0df3dfbf01bdfc1e0a29f64ae228303c726bccda3d05ed5827dec3cfd1fc770e66f45fbe38d8160bcb6d86a99d9f3189e3fd33a8de308ce8f178c6d97d0e924f8dc09e5d484e4f3926bbcb608e23760d7d7e0585d02e50f820db4bbd4aedd0cdae5f11adb607d02d2d50563f1583a967c2abeccacda5d26643b4c1f60ec9382fc8c639f07806310d6d9966a27c5d0a6b8cdb81887a0bf189766880be7e3b5b139c6f6aa3de3fb6599d7f67189637a2ed71ce3878dbdf1fc607f8c34f19984746ce27522bc7e857d025ebf72f17c09cf99c7e74be6388cc5fff75d76f8cc0d1feaaee6f47746e09b037cbc5f02f8e63ae08bf2ccc15ce0e3fdf0dd92f31cf07547e09b077cbc5f11f0d97e5f12bed76e2a7c61efa02b86ffb6c78838169d0a1ff6bbbcdf2ce0b37dcd5ff1f546e0c3fb00bc5f09f059bebe14f0f545e0c36b32bc5f12f86c5f9351652f8ec0d70f7cbc5f29f0591eef057c99087c3846ca409af96c8f9114df92087c4b8189f72b03bee50ef89645e05b0e7cbc5f39f01de8806f4504be03818ff7ab00be6739e05b1981ef59c0c7fb5502df410ef85645e03b08f878bf2ae07b8e03be6747e07b0ef0f17ed5c037e880efb911f806818ff7ab01bee73be07b5e04bee7031fef570b7c2f74c0f782087c2f043ede0fdf877fb003be1745e03b18f8783f3cff1ee2806f7504be43808ff76b01be43edf205cf43ae89c07728b0bcd42ecb52c5f2e2082c2f059697d865099e877c99dd32836b8387592e53957138c484e3c7ec29c83f0ce275b8e578c5c12697cbebc8e759f76f56c5b2c6e04cc2766b04f0b1ee250e5992068b5a72f575617c589747d8e50bce0b6b23f01d012c47596559125c373c3202cb51c0f272ab2cd9f3c22bec9619f4e147033ffbca7652908f757eb465dfe26093cbe575e4f3ac9ed5b37a56cfea593dab67f5ac9ed5b37a56cfea593dab67f5ac9ed5b37a56cfea593dab67f5ac9ed5b37a56cfea593dab7d56c5b2d6e04cc2766b05f0b1eee50e5992068b5a72cd1309e3c3ba7ca55dbe604ecdba087caf049663acb264bf3df1aa082cc700cbabadb264e7d4bcc66e99c19c9ad7023ffbca7652908f75fe5acbbec5c12697cbebc8e7593dab67f5ac9ed5b37a56cfea593dab67f5ac9ed5b37a56cfea593dab67f5ac338555b1ac333893b0dd3a017cac7bb54396a4c1a2965cd7d9c3f8b02e8fb5cb17dc93581f81ef5860d964972578ffc386082c9b8065a35d96e09ec466bb6506f724b6003ffbca7652908f75bec5b26f71b0c9e5f23af279d6fd9b55b1ac373893b0dd7a017cacdbe8902569b0a82557bf14c68775799c5dbea00f1f8ac0771cb01c6f95257b5ff97511588e0796ad5659b27df8ebed9619f4e1db809f7d653b29c8c73adf66d9b738d8e472797d9b3bbb81ffdb27f17f7b08c7f63cfa8f7c53653d6c06b1fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab8fab84e9d55b10c199c49d86e48001febb63a64491a2c6ac975ed3e8c0fdbdd0ebb7cc17d8ee1087c3b8065a75596ec3ba94f88c0b21358de6095257b9f6397dd3283ebfc23c0cfbeb29d14e4639d8f58f62d0e36b95c5e47be6722ebb619c4eadb801b56df063cab6f039ed5b701cfeadb8067f56dc0b3fa36e0597d1bf0acbe0d7856df063cab6f039ed5b701cfeadb8067f56dc0b3fa36e059a7bb0d289661833309db0d0be063dd1b1cb2240d16b5e49a2712c687edee44bb7cc19c9ad1087c2702cb290e584e8ac0720ab09c6c97259853f346bb65f6ab32de04fcec2bdb49413ed6f99b2cfb16079b5c2eaf23df4c61552ca3066712b61b15c0c7ba931db2240d16b5e43a7ec2f8b02e4fb3cb171cdfa746e03b0d584e77c0f2e6082ca703cb5becb2047dcd5bed9619f4356f037ef695eda4201febfc6d967d8b834d2e97d7916fa6b02a96530dce246c77aa003ed6bdc5214bd260514baee3278c0febf20c077c6f8fc07706f0bd3d84ef4c077cef88c07726f0f17ea5c0f72e077cef8cc0f72ee07b27a499ef3d0ef8de1d81ef3dc0c4fb9501dffb1cf0bd3702dffb808ff72b07be0f38e07b7f04be0f001fef57017c1f72c0f7c1087c1f023edeaf12f83ee280efc311f83e027cbc5f15f07dd401dfee087c1f053ede0ffbbf8f3be0fb5804be8f03dfc742f83ee980ef1311f83e097c9f08e13bcb01dfa722f09d057c9f0ae13bdb01dfa723f09d0d7cbc1f5ec3faac03becf44e0fb2cb09c6397a53f052ce7809dcf3bf0f973b1a9fbccf653b01ff29de780efdc087ce701dfb9217c5f74c0f785087c5f043ede0fdbf4f90ef8be1481ef7ce0e3fdb04ff8b203be0b22f07d19f82e08e1fbaa03beaf44e0fb2af07d2584efeb0ef8be1681efebc0f7b510be6f3ae0fb4604be6f02df3742f8beed80ef5b11f8be0d7cdf0ae1fbae03beef44e0fb2ef07d2784ef42077cdf8bc07721f07d2f84ef070ef8be1f81ef07c0f7fd10be1f39e0fb6104be1f01df0f43f87ee280efc711f87e027c3f0ee1fb9903be9f46e0fb19f0fd3484ef170ef87e1e81ef17c0f7f310be5f3ae0bb2802df2f81efa210be5f39e0bb3802dfaf80efe210be4b1cf0fd3a02df25c0c7fbe1f8efb70ef87e1381efb7c0c7fb61fc2eb3cb17dc73b93402df65c072855d96e05b03bf8bc07205b05c6e9725b8fff37bbb6506f77fae047ef695eda4201febfc4acbbec5c12697cbebc8e759f76f56c572a9c19984ed2e15c0c7bacb1db2240d16b5e4ea97c2f8b02eafb6cb17f4e15745e0bb1a58fe60956569f00e9e6b22b0fc0158aeb5ca92edc3ff68b7cca00fbf0ef8d957b693827cacf3eb2cfb16079b5c2eaf23df5459b7cd20561f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571f571fd7a9b32a96ab0cce246c7795003ed65deb902569b0a825d73ce7303e6c777fb2cb17cc09bf3e02df9f80e52f5659068239e17f8ec0f21760b9c12a4b764ef85fed9619cc09ff1bf0b3af6c2705f958e77fb3ec5b1c6c72b9bc8e7c5365dd3683587d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c7d5c9f9971552cd71b9c49d8ee7a017cacbbc1214bd260514baeebec617cd8eefe6e972fb827716304bebf03cbbfacb22c09ee49fc2302cbbf80e59f5659b2f724fe6db7cce09ec44dc0cfbeb29d14e4639ddf64d9b738d8e472791df99e89acdb6610ab6f036e587d1bf0acbe0d7856df063cab6f039ed5b701cfeadb8067f56dc0b3fa36e0597d1bf0acbe0d7856df063cab6f039ed5b701cfeadb80679dee36a0586e343893b0dd8d02f858f74f872c4983452db9e68984f161bbfb8f5dbe604ecdcd11f8fe032cb7da6509bedff7df082cb702cb2d7659823935ffb35b6630a7e636e0675fd94e0af2b1ce6fb3ec5b1c6c72b9bc8e7c9e75ff66552c371b9c49d8ee66017cacbbc5214bd260514bae7e298c0febf20ebb7c411f7e7b04be3b80e56ebb2c411f7e670496bb81e52ebb2c411f7e8fdd32833efc5ee0675fd94e0af2b1ceefb5ec5b1c6c72b9bc8e7c9e75ff66552cb71b9c49d8ee76017cacbbcb214bd260514bae7e298c0febf27ebb7c411f7e5f04befb81658f03960722b0ec019607edb2047df84376cb0cfaf087819f7d653b29c8c73a7fd8b26f71b0c9e5f23af2cd1456c5729fc19984edee13c0c7ba071db2240d16b5e43a7ec2f8b02e1f75c0f74804be4781ef9110bec71cf0ed8dc0f718f0ed0de17bc201dfe311f89e00bec743f878679b7c4fc6a6cec79929d80ff90a1cf0c5e353e72b003ede0ff90a1df02522f015025f2284afd8015f5104be62e02b0ae14bdae50bc60fb322f0b17dc552623956aacc52bb65f6ab32539663a6ca28832071fc4aa1ee383f05f12ab31caf38d8e472791df9a6ca5a1f9b5e56077607ca62d971cd2cc316df7341a6729d2e043d1f7b6ab542eb8af43a6f7f8ede2601dbac4bea32758c79e98218573888312e8390ae80fae6a54910cb6e412cad82584605b1ac17c4b24610cb0a412c6d8258ea05b1f40a62490862e910c4d22d8825238865b6209661412ca571392ced82e2b24e10cb4a412c4b04b1340862e913c4325f104bb320964e412c438258d60a62592d88a55110cb2a412ccb04b12c16c4d22388253ecd2cc9d8c46be049c82f85ed0a8c7dd5b5c10f568fe557697d019453ad758990b2aba0ec4a9dae8e4fdc176354e52046686710d6d956293054c7a79fa54710cb62412ccb04b1ac12c4d2288865b52096b582588604b1740a626916c4325f104b9f209606412c4b04b1ac14c4b24e104bbb20161edf4a6019161497d98258328258ba05b1740862490862e915c4522f88a54d10cb0a412c6b04b1ac17c4322a88a55510cb6e412c4d8258baf2c4c2d70ab9dc4a83653aedd6d8b51b3c13540b76f9da690dc49dedd70247bd5d8e60ee3472a8c5581d777db91e58ea2cd7852ab3c1729c55998d9663a6ca68822071fc983d05f98d10af26076db7c168bbbc8e7c5365c5b96ed3c1eaaafe9b2dd73fcfc7e658361b31c578b7e87421e86bc0dfd996fd55c5b5ea32798e3733b0ad046cf38ad2ecff726dbf15d8b9ffc1791e1d96db858a635b84fea70362d7eea0ff493be8e73bc121f6350dc71fe7b7826f9d0e8ebfb471fcf17a27b0f08273fcd30e58701984743a84a54910cb6e412cd85ea69d45505c4605b1ac17c4d224a8bdac111497158258da04b1d40b62e915c49210c4d22188a55b104b4610cb6c412cc38258da05b1ac13c4b25210cb12412c7d8258e60b626916c4d22988654810cb5a412cab05b1340a6259258865992096c582587a04b1c4a799655f73fc39bf09b6e36b7c6da09ba3d3eda02b08b1c1bf773a4157a8755c86bad67c56f5c4b231462eae69a29d4158675b38c77f4e7cfa597a04b12c16c4b24c10cb2a412c8d8258560b62592b886548104ba7209666412cf305b1f4096259228865a52096758258da05b10c0b62992d88252388a55b104b8720968420965e412cf58258da04b1ac10c4b246100b5f5790c0b25e505c4605b1b44a6211d45e760b8a4b932096ae1016cbd73407f03a242fc66ae8754ec5d26d9725f86e545704966e60996bb98e5499f3ec9619cc7b9e6fb94c55c6020812c78fd953903f1fe2b5c0419b9e171f5f4fbc8e7c9e75ff66c5b9d5cc9984ed3a05f0b16e2eb0d8ee0b94cf3c1ee0b2d5fd9b8da9319b96e7f52f55c561ff3d080c6c2b01db9c5935c635a4b94a211fef3db587d45f9b83fa6b37ea8fd7dbe0bcc9beb4034bbb03960e8345925ddbb1c73ae6c5581d776e6e0316dbcfd3a4806536d86971e0736b049f5b80c5f2f34d9914b034831dcbcf8405fd125fc36b827ee9d494b3fa0cfaa536a35f62067c3e8ab719867ee974e897da428e8356cbf18903935ad260b715fac3d690f669fb993765a72542fbc47663f799be4cd06e9a22b034008be56741330e9ec9ed77f06ced84e78d9f7ae613c6229c5f07f1aa75704ea937ce29bc8e7c9ed5b37a56cfea593dab67f5ac9e75ff66552cfcfb03e735f3762d02f85887ef7cb1fddb20785f466c7c3da9dfa6d7c16f66bbbfd333c1ef50fc3d970606b695806d8ea81ce3ba017e33371b7595845861fd3538a8bf46a3fe789d6de17d6dacbf46072c4d06cb33d7eec026fbc740a69fdfbfd36cf457669d2adb96dfdb155ca7ab8338aac5581d77eda1c661bfa4caacb6dcbfa832aac021f6b51afa39cec7f703b87c371d976bbe9b0e9fc52900966a072cb80c42ba3a8465b7209651412cad8258d60b62592388252388a55e104bb720964a412c49412c09412c3806996e96d982e2d22e886589209606412cf305b15409626916c4522a88a55010cb902096b58258960962e911c4522d88252588a548104baba0b15493a0b8ac10c4d22688a557104b8d20960e412c6582588a05b10c0b6259278865a520963e412cb582583a05b1940b6299258865b5209646412cab04b12c16c4522788a54b104b85209612412c2d8258e2d3ccb2aff733727e1d6c677eeb0bdfc588e5f13d0ede5efd4e3fb77a62d905b1896563399cc618553a8811da198475b685ef67ac8e4f3f4b8b209612412c158258ba04b1d40962592c886595209646412cab05b1cc12c4522e88a553104bad20963e412c2b05b1ac13c4322c88a558104b9920960e412c3582587a05b1b40962592188a549100bdf1796c05224282e29412cd582587a04b12c13c4b25610cb90209642412ca582589a05b1540962992f88a54110cb12412ced8258660b62691034964a088a4b52104ba520966e412cf58258328258d60862592f88a55510cba82096dd8258cc7bdf98afc6e57cbee27bdb09c83f54df4c50fff6f58cb80bbf70198c4de4ded733e2d3cd322a88a55510cb7a412c6b04b16404b1740b62a914c49214c49210c4d2e0f8dc168565b6a0b8b40b62592288a54110cb7c412c5582589a05b1940a622914c432248865ad20966582587a04b1540b624909622912c4d22a682cd524282e2b04b1b40962e915c4522388a543104b99209662412cc38258d60962592988a54f104bad20964e412ce58258660962592d88a55110cb2a412c8b05b1d40962e912c4522188a544104b8b2096f834b3ecebb96cce4f80ae42ebf07b7be53add00ba82101b5c4e05e8f89a1c97a17ecf5f503d91a10018ca42b8787fb4c776cae213f7751d73b43308eb784f9f19ca1c5f37980a4b8b209612412c158258ba04b1d40962592c886595209646412cab05b1cc12c4522e88a553104bad20963e412c2b05b1ac13c4322c88a558104b9920960e412c3582587a05b1b40962592188a549100bfff696c05224282e29412cd582587a04b12c13c4b25610cb90209642412ca582589a05b1540962992f88a54110cb12412ced8258660b62691034964a088a4b52104ba520966e412c19412c6b04b1ac17c4d22a88655410cb6e412c05064b29e4f335abe0f7a5d67582ae54ebba4097d4ba6ed09568dd7cd0cdd2ba1ed0156b5d2fe88ab4ae0f74855ab7187409adcb80ae40eb96802eae75cb40c737a35780ee499d5e09ba27747a15e81ed7e9d5a07b4ca7b95f50e793470d9daaf347747a50ffef7f7a4b50e76c87cbe5f547806faf4e3f0a3a4eaf05e6870d9d627ec801f3c30633af3f047cccff30e838bd0e98f7183ac5fca003e63d0633af3f087cccbf07749c5e0fcc0f183ac57cbf03e6070c665ebf1ff898ff01d0717a0898ef33748af95e07ccf719ccbc7e2ff031ff7da0e3f43030df63e814f3dd0e98ef319879fd6ee063fe7b40c7e95160becbd029e63b1d30df6530f3fa9dc0c7fc77818ed3bb1df3ed35f8f61a7caeec3e62d87d244f761f32ec3e9427bb0f1a761fcc93ddfb0dbbf7e7c9eebd86dd7bf364f76ec3eedd79b2bbbfb5e7e9ea37f6b7f63c5dfdc6741d47fe7c941fbbfe7c941fbb53398e6eb76b772009e5f31237d607217d3bb0dc613906aaccdbec96d9afcafc9fe5325519b7424c387ecc9e82fcff41bc6eb51caf38d8e472791df9660a6b1274f81c05e7a740778bd69582eebf5c0ee8fea37525a0bb59eb6681ee26f61d74ffd6ba22d0fd4beb1e07e67feaf463a0fb874eef05dddf75fa51d0dda8d38f80ee6f3afd30e8feaad30f81ee2f3abd077437e8f483a0fbb34e3f00ba3fe9f4fda0bb5ea7ef03dd753a7d2fe8fea8d3f780ee0f3a7d37e8aed5e93b41778d4edf01baab75fa7fa0bb4aa70b21f6576a5d0274bfd7ba02d05da17571d05d1e63e598ee775af524d8bd4ca79f00dda53a7d17e8f89aed6da0e3fb67d89e792ec32da0e3394bff051dcfd9fc0fe8789ef8cda0e367536e021ddf2bfb37e878dec2bf40c7f393fe093a9e9ff90fd095ebf4df41c7cfa1dc083abe2ff637d0f1bc80bf828ee722fd05743cfff106d0f19ceb3f838e9ff3f813e8f8fdc2d7838eefbb5f073a7efee08fa0e379747f001dcfabb81674fc3cd235a0e37b2957838eef255f053a9ec37825e8f8fef7ef41c7d7f6af005d5aa72f075da74eff0e747374fa32d0f13d006eafaafda876d564f9be4610336390906bccd004f7581aedb2f4a780a511ecd45bb533d0af8ae37bf5ead84fc7c6eab91eecd6d9b19be1842aae16caef040eb695806d5ea30fca72bd7dade578c7c16e918e03f3d4020f6fb341f3a87eb0b0ca593b08c6b14d11da64a3fd3a6394fe24c4642a2c75ceda6d764c6dbb0da8326b2c97a9caa88620996d2a05f93510af6a07f76c6be3e3eb89d791cfb37a56cfea593dab67f5ac9ed5b37a56cfea593dab67f5ac9ed5b37a56cfea59670aab62e1ebe7f81e45deae49001febf07e8bed6bdb78af8ecb56f72eeeab1cb3d960d566f6de12de8f480303db4ac03607548d71edd15ca590df04f5571f527f750eeaafdea83f5e675bf8ac2bd65fbd03960683e5996b776093fd6360a05fcd0908ee9f1bfd9559a778efb50e8e15becf17bcb746eb5ba04d3618bae9aa0f3c661a42da69b3b37e26e3e21e5af0d85d35d443a3716e49407e25f421d555637563f6172a3f1d92cf8bb13aeebe26deb7b6fc6c65d0675640f9836003ed963b68576c37ae856d54409c39ddc11362603b8cefb8776ac4276e87e97a639f14e45739f6b9d238962a0d56d54eeaa04da5e15ebfedf30dfa8b71e1f91829c8c7ef4bd638880b8eefd2c0500375d462f43b38aec23eb02a8fe3aaaa9071551df0b11fd89f5c971a6375390632e7d734d83f4f8c9b5f83f32f3a81a31e8e67de6640b76b35bfc61cf7a4615ffc7e673ee60499e7669c13b40298f33527a8dee88b3186aba09f381ec60a61e3d73a43e7803f83fc5c6e9dc18fe3026c2fb67f0b61dfc08bb13aee1c5bedb07f73d02705f37cf05c12d61f713ecff374307e08eabccaa8f32ae3fc1a33b6a90d193fd6859c7f2b2cf78d2ecee9aa8c32e8db1b8cf37902f20f87e3f508180bb2cf8d50ce86907c5e72b5631ccb953ae89f92c658b1343ed16e8983369634c68a4f3d870071e6f4b130562c8160717c4ba1ddf1b310b85d4948bb2d85e38af3538e7d2e358eab528355b593a3a04d6d80b1a2ede31cfdc5b8f0f93905f9f8bbb6c938aef1db1d782e7231d62e33e257661c9ba9d8c4f7c8e0b9d4fef82ccbd5688c75cc6b5478ed0cfb04bc7636dbc1f9b22d423f331beaaed92ecb52555c0b1cef836003bf2ddfe8a06e9a0cbb2d46df9c823835858c69da216838164a87ec638e35719c86e7463e5fb642793c4629884d1c63a925ec7a39fe9662ce6ab05365d8b17e0ece8c1fb71480cd0a606b358e49c551e6a0aecb8dfec17c9f213e9782bfcf4f4d8dc58cebb5138f5d43a7f8db1cf0b71bfcbcde0675da61b4cb6288af1d96ec3cfd287d07b62fdbfd982ab3c56e99fdf6fbb8951b5519f89c05c7af25e4dc89d74f9b1cb4a316a31db5849c9fedda1dd8acca689cc4ffc6108ec63cfadfe8cceef24daa8c8649fc6f08e168c8a3ff0dceec2e1f5265d44fe27f7d08477d1efd7777bf6945701eac9bc4ffba108eba3cfa5fb78fb14c2ed6866966b56f7769d057d74ee27f6d08476d1efdaf756637b35c95513389ff35211c3579f4bfc699dd0383d766564fe27fbee69feccbff6a6776335b54195593f85f15c2519547ffab9cd95d19b4ffca49fcaf0ce1a8cca3ff95ceecaedca0caa898c4ff8a108e8a3cfa5fe1cceec6e0f5bae593f85f1ec2519e47ffcb9dd9dd70a02aa36c12ffcb4238caf2e87f9933bb0341fda726f13f15c291caa3ff2967763707e39fd249fc2f0de128cda3ffa5eeec06e7bfe424fe2743389279f43fe9cceee6e0b75ac924fe97847094e4d1ff1267763706f53f6b12ff678570cccaa3ffb39cd91d0aaed5144fe27f710847711efd2f7666777950ff4593f85f14c2519447ff8b9cd95d129cff0a27f1bf3084a3308ffe173ab3bb65a92a233189ff89108e441efd4f38b3bb2138fe0b26f1bf2084a3208ffe1738b33b10fcfe8f4fe27f3c84239e47ffe3ceec2edf983590dbff5808472c8ffec7dcd90dfc7f3296dbffa7f281e3c958fefc675bf6ed6e0efaff2762b9fde77ce478228ffe3fe1ce6ed0ff3d1ecbed3fe723c7e379f4ff7167765706fddf63b1dcfe733e723c9647ff1f736677e30655c6de49fce77ce4d89b47fff73ab3bb3418ff3e3a89ff9c8f1c8fe6d17fe46b093957f13b47836f681afc8ad5f2f7ab96aa321fb6ecbf2a83df5bace6a1f0dc0eb69380fce5d563db1da8d349f099e7ada8fcd521f9bc18abe3e67170cc94af96bf4b15cc357d00ca1f041b68d7e5b7a5e25ad806eb13903e985fca1b1bff9e7d8e2f7e436b4fc876986e31f64941fe1ec73eefebdb046c4bb59355d0a6b8cdb838ced15f8c4b2bc485f3715e93ede34d156d7e2b82191e06964ea33f51c711b7d14ee0dbe3806f5fdfd0d8037cacc3f7a5b31fd89f6c84796d9d5a8fdf4d4cebf41c28a7dbd0293fe739f093ed70b9bc3e0ff8f8bdcdddf9e51b982adf5c834fb12cb01cab24d8e425571fbe0058e65b665165f6d82d339837b310f8d957b693827c7cee65a165dfe26093cbe575e4eb0961e5f7a9e377887b80d572cc02d6b906ebdc10bb7d798811f72b7d8eedf61a76bb0cbbea38c17a524baee3a417780fb0ccabca5c64b7cc60fcb4189807c14617e8fb1dc47e31c4330e36589f80f469307eea1f4b3e75ae616675acf4856c87e985c63e29c8ef73ecf322e0188475b6a5ceaf3b61cc64b9cd07f5dd6730f07a17e81785c4ab0fe2c5f9dda0e33100f6b1738c3292500e9e6b2db7eb4c2e3fb1de59d70b7c8b42f816dbe51bc8d51616031feb0e001617fda0c9c2e71eec071b8df805cf920157c23257b12ed76699f88d185e72f5e7c5f0bfc8328bfab68c1e4ac7768decd8b9e1b82d476cd9b019af54171a88058086690e3d7ec6a61074fc199b22d0f1feb340c7e594409e0bd7d1372eb7d0602c06365b76f17340bce4aafe59c062bb29aa18f3e78074f51fbd73ebc816acff228331acfe555ec2d88eebadd03233dae272799d6d29bff89346276cd8f4fae7ed3c6e74fb96e1915d1868b361c70d87d48295858d99b72b329cb5dc58964a6b2cfc0da74d1bb66d3b7c74e3b6ad9b0e1e1dde34b275c7705893e1886175857519b8edbeba8a2478c63a2eab0474d80d7144cc88590b493d94c9cd27a19d298a8d358992d8d827ba822988b16cfb549fd8524fcaaaa778d5884b85577d12ab0ecafdb9feaf6ca94f5ea953a0faa495fa8495fa6495ba3ca21efd53af4e518fe2aa21503a96fdf9a2860f6a48a57e42a96192fa59a77e3aab9fd26a88a486446ad8a14eedea34ab4eabead4af8682eadad600c9121235af40dd5b53f38b5790a87996ea5ecbb34856911c44f26c92e7903c57c7f77924cf277901c90b495e447230c96a924348d6901c4af262929790bc94e4652487911c4eb296e4089223495e4e7214c92b488e265947f24a925791bc9ae41892d790bc96643dc9b1b1ecb040dd0f55cf2fa9fb62eadec010c97124af23d94a723cc9eb49b6916c8f653f51bf83e404923790ec24d9453212cb7e0afe449293484e263985e48d246f223995e434923793bc85e47492b792bc8de4ed246790bc83e44c927792bc8be4dd24ef21792fc9fb48de4ff201920f927c88e4c3241f89653ff1fe51928f917c9ce413249f24f914c959249f26399be433249f253987e473249f273997e43c922f907c91e44b24e7935c40f26592af907c95e46b245f27f906c93749be45f26d92ef907c97e47b2417927c9fe407243f24f911c98f497e42f253929fc5b26df517241791fc92e462925f91fc9ae41292df90fc3696fd5cd965b1ece7cd2e8f653f87a63e93a63e9fa63eaba63eb7a63ec3a63ecfc607321ec43c758f7fa26f1819d9b2fd8491f4c88ef4f6d16d235b4fd8764afaa4ad23af4bef3871cbcea16d3b4ec29dfffd7476be45afcc9eb8f386cd9bf7bddf237a853f78b76678f39693d33b4647d23b86d21b778c0e6f1e776e3a5a7b7d905e3f327b724eefdab66324dd9f1ea6bfd403ef3869cbe64569ccdb452eec1a49ef1ad9b073243db473c7f6746611967b54a99b728fa97c1a31cd543d8d9d97574dbd42fe0f6b585bbcfe1603009b2d6c6f0000001c481f8b08000000000000ffed9d077815c7b5c7efd54582ab2b01a6096424ad2aa24b028c316084e9cd0df70a06516c40188471efbdf7de7bc1bdf7827b27cf7e71123f3bcdce4b9ec94b5e122771e2e49dd93b07fd19966bedc70cda0b67bfefcfee9edd9df39b3367677767579775b1582c1e4b4f09528fd8a6136f6fd4f3ba2d9bea2d9655e792339e259c3959c299c812ce0e59c2999b259c7959c2d9314b383b650967324b38f3b3843395259c0559c25998259c9db384b34b967076cd12ce1db284b35b967076b7c8590c9cfcccd453cf7be979919ef7d6f33e7acec7eea8e77d755d3be8f5125229a98ce4e96d1c98725205a9925445aa26d590fa916a49fd4903480349834883494348437519eae1ab81348c349c3482b41369246967d228d22ea4d1a431a4b1a45d49e374ecc69376234d204d244d224d264d214d254d234d27cd20cd24cd22ed4eda83b4a7ae8ba7ebb217696fd26cd23ea47d49fb91f6271d403a907410e960d221a4434987910e27cd21cd251d419a479a4f6a222d202d242d222d261d493a8ab484b494b48cd44c5a6ec4fc68d20ad24a528bc1b98a740c6935e958d271a4e34927904e249d443a99740ae954d269a4d3496790ce249d453a9b740ee95cd279a4f34917902e245d44ba987409e952d265a4cb495790ae8c6ddcfe5791ae265d43ba96741de97ad20da41b4937916e26dd42ba95741be976d21da43b497791ee26dd43ba97741fe97ed203a435a407490f911e263d427a94f418e971d213a427494f919e263d437a96f41ce979d20ba417492f915e26bd427a95f41a692de9755d971c5d9737486f1ab6b7486febe577f4fc5d3d7f4fcfdfd7f30ff4fc433dff48cf3fd6f375aabcc2f4b2ba1735c736948dcfd938d8f8fccd011b9fcb09b0f179dd016c7c8ee7828dcff73cb0f1b9df116c7df57227b095c032cf4bf5723ed8caf4720a6c9e5e2e005bb95e2e045b855eee0cb64abddc056c557ab92bd8aaf5f20e60abd1cbddc0d64f2f77d7738e859a1af5bc6e0b2755a6e56b469d62e73ce801f5e13ce80936ce835e60e33c28021bd7bd37d8380ffa808df3a0186c9c073b828df3a02fd8380f307f380f4ac1c679500636ce030f6c9c07e560e33ca8001be74125d8380faac0c679500d368e6f0dd838be9c3f2a9e93613b4f78aee27826db783b9eab0928936dbc1dcf55de8ee72a6fc77315b7f39cb7e3b9cadbf15ce5ed785ef2763c07b90df17ce363ba838ddb10f393cbc15ce436c4bce3b231c7b80d31c7d81fe618b721e61833608ef139e2818db930c7f81cc11c6356ce3155d73ce06dd4f3ba2d9beab1efe6296eac37c232fb572c7dedb2d4a580a52ff829b5eb67185e73da52e75260f12cd719af6b6d61f180a5dc2e8b3f865961b74cbf4d39d6786eb09f12a84fa5e5fac4c10f97cbebec2b05360ff82a03f8aaecf235c4c10f97cbeb55c0c7b67277b1aac3fe8acb56d789c3c1a7e5bea74ed59fcf731ee76706f695807d9614b672cdd3b67cd85e0c363e1efb6dcfb0b93887f0998acbe5f572e0e33ec5dbba7c0d6de52b33f81cf47d7eacca0c16f16bbdcdebb07c75eef0f9c27e12b0fdbb78eb7eab81a9d82e5343d86b303e5fdabd46d5d7e1b9d716960a6071710db6ddb73bb876f939550d31f1f41cafabbcbd0ae2556d395e99aea1c827acc22aacc22aacc22aacc22aacdb372b8e4b797abeb9719ff6e2db5ae33e3c268de33e6f834fcfaacf7a7fdca7d828bfc8688b04ecf353e0e2f7a238ee83633cc586cdc1f8713df273b9bc8eef2d790c0aeb6af939da67d9d160d976fd36cc536596592db3be4ebd6fc277d365463df03d4f1fc3a672f2fb786b7d3dab6ce1dfcf78b15616dbe348f87ec65d9fd45087e3f639b18dfb874af06b694ca39e1794df6a237eccc1be12b0cf577a5e6897670357a6f14287d74f3f0e55e0d78bb59e1bf82edbbc96abe36a80cb83f85543fc789ff57aaecea14be11cb23de61d767c0f73db6e9b36d4e1bbafb6b0603bdb7e3fe6227754993596cb5465f4839898399582ed987ffd2cc70bfb072e97d7914f5885555885555885555885555885555885555885555885555885555885355b58158b6770e2f8bf17013eb6e1fb21db63dbf8b75a5cb67a777132bcbba8b0ea33fd2e0cdf4778b14dff5e2c01fb3c03df6f9fae97f361bba7e7f8ad8307e5bb7887f5437fe3921fdbf4ef725cbcefc0f78a5ceeb6eb37fdaedcee39d0b0e15d39e79379dee1df2d151b367ccfa7dadcd376fc7ea3c2b0b5577be0395301365ec66f1cecc6b8dec53b34fff708fb413b70ddd84f02b6df007dc84df1d6b631fb0bb57d4dc0769ee2c67a232ce37bf6fe76ebeaf79903a0fc46f0817e07daf55b8f7ee35aec83ed09587e000234b07571437c9959e55d6dc07eb85c691c9382edb58eebdc1f381a619d7da93cb915726a0d5c2f6d5f6fb0be189722880b6faf80b8d83edf5415f1fece03861a60293138f1be0afbc05a077c9bbbafaa053ef31b0ffc5e10fb13fc5ed0e53d508e114b6c434bd7898dbe07c2ef2fca81837d25609f97745eabef81ccfb1e0f8eed0d656e8d6f98cc6b337ec3b41698e51ba6e06f98cc6f73f11ba6f7a05ffb16ee6d2ac02ffbaa326c0eea5d6fd6bb11d6d917dedb60bdab36536ffc2d0617fd2473e51a1c786fc2fb7c0af9aa26ec63f9fb53bc87339f15eddf6f34f8f76b03ac9699be8ee1f593db0bef2378fbe790835fc0fd18d7b902ca591fb09da74cf76bfd217e83edd6d53f578740f98de003fd0eb5ebb71efdf2fd1afb607b0296bf81000d6d5ddc105f6656793728603f5cee671c9382ed831cd779307034c23afb5279f20bc8a9f570bf66fb3e04eb8b71e90d71e1ed3836e619fbab7ce6f3c183f8d93e2ff19e9ecb35eff183eebdf1fa60ff1e29cd550e5c5e2cf87785d8560e5c387e65f9ef04eaf09bf962f089dfcc47e5378d70cc2227d6ca57e280af6f08be12e0e3e312c067f7ef21d27ca521f8f077f9f0ef2298afdc019f1782af1cf8f8b85ce0b33dbe14f61befa0df22ca83b9ed7b44bc176d0b1ff6bb7c5c47e0b33de68fbfafd8163e7c0fc0c775023ecbe34b3e5f6d083e1c93e1e392c0677b4c46953d2004df40e0e3e3f281cff2fd9ecf3728041fde230d8265e6b37d8fa4f88684e01b0a4c7c5c01f0d53be0ab8bb59daf1ef8f8b842e01be680af2104df30e0e3e33a03df08077cc343f08d003e3eae0bf08d74c0b75308be91c0c7c77505be510ef8760ec1370af8f8b81d806fb403be5d42f08d063e3eae1bf08d75c0372604df58e0e3e3f03786c739e0db3504df38e0e3e37a00df78077c8d21f8c6031fdbf1fa3bc101df6e21f826001f1fd707f826d9e5f3ff1e726208be49c032d52ecb70c5323904cb5460996297c5ff7bc86976cbf4c706a75b2e5395310362c2f163f6146c9f0ef19a61395e71f0c9e5f23af209ebf6cdaa58261a9c49d86f6204f8d836c5214bd2605153a6be2e880fdb72965d3effba303304df2c60d9d32acb307fdc70f7102c7b02cb1e5659d2d785bdec96e9f7e17b033fd795fda4603bb6f9de96eb16079f5c2eaf239fb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb00aabb0da67552c330dce24ec3733027c6cdbc3214bd2605153a6ef4482f8b02df7b1cbe77f53333b04df3ec0b2bf5596f4ff3db16f0896fd81653fab2ce96f6a0eb05ba6ff4dcd81c0cf75653f29d88e6d7ea0e5bac5c12797cbebc827acc22aacc22aacc22aacc22aacc22aacc22aacc22aacc22aacc22aacc29a2dac8a65b6c19984fd6647808f6dfb3964491a2c6aca34ce1ec4876d79b05d3eff9dc44121f80e0696c3ecb2f8bfff70480896c380e550bb2cfe3b89c3ed96e9bf939803fc5c57f69382edd8e6732cd72d0e3eb95c5e9f037661ddbe5915cb41066712f63b28027c6c3bd4214bd2605153a67e694e001fb6e51176f9fc3e7c6e08be2380a5c92a4bfabdf2bc102c4dc032df2a4bba0f5f60b74cbf0f5f08fc5c57f69382edd8e60b2dd72d0e3eb95c5e5fe8ceaf5fff453f50ff45011c8bb662fd91afadacd3b38855e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae125789abc455e22a7195b8b69d55b1cc353893b0dfdc08f0b16dbe4396a4c1a2a64c63f7417c987747dae5f3df732c0ec17724b02cb5ca92fe4deaa342b02c0596255659d2ef3996d92dd31fe76f067eae2bfb49c1766cf366cb758b834f2e97d7916f5b645d9845ac92036e5825078455724058250784557240582507845572405825078455724058250784557240582507845572405825078455724058db3b0714cb62833309fb2d8e001fdb963864491a2c6acaf49d48101fe6ddd176f9fc6f6a9687e03b1a585a1cb0ac08c1d2022c2bedb2f8dfd4acb25ba6ff4dcd31c0cf75653f29d88e6d7e8ce5bac5c12797cbebc8972dac8a65b9c19984fd9647808f6d2b1db2240d1635653a7f82f8b02d8fb5cbe79fdfab43f01d0b2c273860392e04cb09c072bc5d16bfaf39d16e997e5f7312f0735dd94f0ab6639b9f64b96e71f0c9e5f23af2650bab62596d702661bfd511e063dbf10e5992068b9a329d3f417cd896a738e03b3904df29c0777200df690ef84e0dc1771af0f171f9c0778603bed343f09d017ca7c332f39de580efcc107c6701131f57007ce738e03b3b04df39c0c7c71502df790ef8ce0dc1771ef0f1719d81ef02077ce787e0bb00f8f8b82ec0779103be0b43f05d047c7c5c57e0bbc401dfc521f82e013e3e0efbbfcb1cf05d1a82ef32e0bb3480ef0a077c9787e0bb02f82e0fe0bbca01df9521f8ae02be2b03f8ae71c0777508be6b808f8fc331aceb1cf05d1b82ef3a60b9de2e4b5d0a58ae073f373aa8f30db1b6d799fda7e038e4bbd901df4d21f86e06be9b02f86e75c0774b08be5b818f8fc39cbedd01df6d21f86e073e3e0efb843b1df0dd1182ef4ee0bb2380ef6e077c7785e0bb1bf8ee0ae0bbd701df3d21f8ee05be7b02f8ee77c0775f08befb81efbe00be350ef81e08c1b706f81e08e07bc801df8321f81e02be0703f81e71c0f77008be4780efe100bec71cf03d1a82ef31e07b3480ef09077c8f87e07b02f81e0fe07bca01df9321f89e02be2703f89e71c0f77408be6780efe900bee71cf03d1b82ef39e07b3680ef05077ccf87e07b01f89e0fe07bc901df8b21f85e02be1703f85e71c0f77208be57808f8fc3fbbfd71cf0bd1a82ef35e0e3e3307eafdbe5f3dfb9ac0dc1f73ab0bc6597c5ffbf06de08c1f216b0bc6997c57ffff3b6dd32fdf73fef003fd795fda4603bb6f93b96eb16079f5c2eaf239fb06edfac8a65adc19984fdd646808f6d6f3a64491a2c6acad42f05f1615bbe6797cfefc3df0dc1f71eb07c689565b8ff1b3cef8760f910583eb0ca92eec33fb25ba6df877f0cfc5c57f69382edd8e61f5bae5b1c7c72b9bc8e7c6d655d9845ac125789abc455e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae12d7b6b32a96770dce24ecf76e04f8d8f6814396a4c1a2a64cdf3907f161defdc82e9fff4df8ba107c3f02964fadb234f8df84ff4708964f81e513ab2ce96fc2ffd36e9975aa8c1f033fd795fda4603bb6f98f2dd72d0e3eb95c5e47beb6b22ecc225689abc455e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae125789abc455e2ba6dc655b1ac333893b0dfba08f0b1ed13872c4983454d99c6d983f830ef7e6297cf7f27f15908be9f00cbe7565986f9ef247e1a82e57360f9995596f43b89ffb25ba6ff4ee20be0e7bab29f146cc736ffc272dde2e093cbe575e4db1659176611abe4801b56c90161951c1056c90161951c1056c90161951c1056c90161951c1056c90161951c1056c90161951c1056c90161951c10d6f6ce01c5f299c19984fd3e8b001fdb7ee6902569b0a829d37722417c98773fb7cbe77f53f36508be9f03cbafecb2f8ff7fdf2f42b0fc0a587e6997c5ffa6e6d776cbf4bfa9f90af8b9aeec2705dbb1cdbfb25cb738f8e472791df98475fb66552c5f1a9c49d8efcb08f0b1ed970e5992068b9a32f54b417cd896bfb1cbe7f7e15f87e0fb0db0fcce2e8bdf87ff770896df01cb6fedb2f87df8ffd82dd3efc3bf017eae2bfb49c1766cf36f2cd72d0e3eb95c5e473e61ddbe5915cbd7066712f6fb3a027c6cfbad4396a4c1a2a64cfd52101fb6e5efedf2f97df8fa107cbf07963f3a60f9df102c7f04963fd865f1fbf0ffb35ba6df87ff09f8b9aeec2705dbb1cdff64b96e71f0c9e5f23af2650bab62596f702661bff511e063db1f1cb2240d1635653a7f82f8b02dffe280efcf21f8fe027c7f0ee0fbab03be6f43f0fd15f8be0de0fbbb03bebf85e0fb3bf0fd2d80ef1f0ef8be0bc1f70fe0fb2e80ef7b077cff0cc1f73df0fd3380efdf0ef8fe1582efdfc0f7af00be78dc3e9f0994898ffda76047e4eb6097cfbf7fc809c1c7fe154bc272ac5499b976cbac5365e6598e992aa3230489e3970b6dc7dbf3205e1d2dc72b0e3eb95c5e47beb6b2f68cb52fab03bf0d05b1f47d4d8ee18bdfb9205327bddc01ec7ceea9d524c74eaff3fed7eb7d12b0cfb0647a5ea863cc530ec438e920c63835c27212fb153df589104b518458ba4488a520422cc908b1e44588251121965e1162e91a2196c208b1e44788a56384583a4488a577845872e3d161e91ca1b8a422c4d229422cb9116289b7334b32b6e9f36c12b6e7c27e39c6b12a8ef716b66e4f697b0e9453a06d8980b2535076be5e2e886f7a2cc628e52046e8a711d6d9573e3014c4db9f2537422c9d22c4928a104be708b1e4c6a3c3d23b4271e91021968e1162c98f104b618458ba4688a5578458121162c98b104b32422c051162e9122196a208b1f489104bce5662e1fb7e2e37df60694fbf8576fdfadfea7406bffc1c54087167ff9d81a3b35d8e119b7bdfd2c5419c716a8465f6b5b9f72dedcd52142116ccc9f666e912a1b814448825192196bc08b12422c4d22b422c5d23c452182196fc08b1748c104b8708b1f48e104be708b1a422c4d229422cb9116289b733cbe6deb7f0767c4fd2d5781650b61d8ce7147c2f833eb89cae60e33e84cb50edf278e1a60cf8fea65b00171f8ffed84fb7f8a6c7ba8e7937e379b69bc187ef6fba39be976e0b4b6e84583a458825152196ce1162e91d21960e1162e9182196fc08b1144688a56b84587a458825112196bc08b12423c4521021962e1162e17bcf28b01445282e7d22c49213c0d2c32ecb707c66e0c958dde819a807b074b71c1755664fbb65fa7f1bd5cb7299aa8c220812c78fd953b0bd17c4abc8411ef58c6fdc4ebc8e7cc2ba7db32abfbdadfa4dff7f833d43f41bbd1dc64095d9c7c1395e0c15e2baf681f62d0e68df6207eddbc7685f5e473e61155661155661155661155661155661155661155661155661155661155661155661155661155661155661155661b5cfaafcee68d56f83ffcd05fa5593b1bac1570cfcbb88812ab3afdd32fd6f2e4aa0425cd7bed0be2501ed5be2a07dfb1aedcbebc827acc22aacc22aacc22aacc22aacc22aacc22aacc22aacc22aacc22aacc29a2dacca6fa95dbffedf17a35f3519ab1b8d59973a8c812ab3cc6e99fe98b50715e2ba9641fb7a01edeb3968df32a37d791df98475fb66557ecbadfa4dbf972a0b718e973b8c812ab3c2c1395e0915e2ba5640fb5606b46fa583f6ad30da97d72bddf9f5eb5ff503f5af0ae0a8da8af547beb6b2f6ca225689abc455e22a7195b84a5c25ae125789abc455e22a7195b84a5c25ae125789abc455e2da7656e5b7daaadff46f62a25f3519ab1b8d83573b8c812ab3c66e99fe38703fa810d7b506dab75f402ef673d0be3546fbf23af26d8bac9512d7ed9e55724058250784557240582507845572405825078455724058250784557240582507845572405825078455724058250784b5bd7340f9adb5ebb72169f85593b1bac1570cfcbb88812ab3bfdd32fd6f2e064085b8aefda17d0704e4e20007eddbdf685f5e47be6c61557e073ac8c5fe217271a0c318a8320739c8c5c15021aeeb2068dfc101ed3bd841fb0e32da97d7912f5b589360cb89b5da787b026c43b4ad03d8866a5b2ed8eaa04e6cabd7b68e606bd0b64e601b065c3c1fae6d7dc03642dbbac0fe3be9e5ee601ba9977b826d67bd5c04b651ec036cbbe8e562b08dd6cb7dc136462f97806dac5e2e03dbae7ad903db38bd5c01b646bd5c05b6f1c6b555d97633ae71ca36c1e8f7946da2d1ff28db2423ff946db25ece8fb5daa640ceb26daab615806d9ab615826dbab67506db0c6e37b0cdd4b6ae609b15c0c7f939086c9c9f98cf9c9f43c0c6f939146c9c9f7560e3fcac071be76703d8383f315f396ec3c1c6711b01368edb4e60e3b88d041bc76d67b071dc46818de3b60bd876d0b6d160eba66d63c0d65ddbc682ad87b6ed0ab69eda360e6cbdb4ad116c45da361e6cbdb56d37b0f1b93c016cc5da36116c3b6adb24b0f5d5b6c9602bd1b629602bd5b6a9602bd3b66960f3b46d3ad8cab56d06d82ab46d26d82ab56d16f46ff9c0c875ca87ba302bfa665b1ef86ed4f3ba2d9bfc54663f5c2eaf97031fc7cbdbba7c0d6de52b33f8fcdf9bb0cb528f6dcf53dc586f84e54a60a9b0cce27f9b6db74cff3eaa1af8b9aeec2705db7b42ddaa2dd72d0e3eb95c5e47beaa0056ee77544e78c67e0e6296fe6d1b83b52cc06fed568811f72bb58efdd6187e4b0dbfea3cc1765253a6f3a40678fb59e6f59f7ded96e9df120c00e646f0510af68196eb827ee35aec83ed0958be9a6f1c603f35f1b58699d5b9521bb01f2e571bc7a4607bade33af7078e4658675fea9eee82c25606cb39efb777adc1c0eba560ef1f10af5a88176ff7c0c6f700d8c796186524a11cbcd65aceebfa4cf5c476675b0df0f50fe01b6097af21532e0c003eb6f5031617fda0c9c2d71eec077b19f153b97a3870252c73e5c55a9f5d6c9589cf4d3c65eacff3609e6b99453d6ff173d3ca96e615731736eddd34777e1cb03a1888398086cb387cc18f76387cc18f76387cc1c7e3500597d309b6a9657ea46a5abab865df654dcbe6ad386e794bd3fc99cd0b3178b906cde628912e0fbc35ea79dd964d0df8d0cb53a646ee042c1dedb2f8499cb45ba67f739b0ffc3800a5a6146ccf857df22dd72d0e3eb95c5ecf77e7b78eeb98a9fea9008ed456ac3f0e24250338793b9e7d3830c9f384519edadfcc656b15ea0165c635900250276b6eacf504e9146b1d0952606aa447ddb4a8911c3572a3466ad4c88cea36ba43992fe9b9f2a39ebad415458da4a891133552a2ee20d41d9dbafb57575f7547a29e40bc58faa9483d79aa27517587a1ee28d4555b5d19d5554a5d95d49553dd490d220d260d210d557121d5931a48c348c34923483b91469276268d22ed421a4d1a431a4bda95344ec7773c6937d204d244d224d264d214d254d234d274d20cd24cd22cd2eea43d487b92f622ed4d9a4dda87b42f693fd2fea4034807920e221d4c3a847428e9b058fa0a3b8734977404691e693ea989b480b490b488b4987424e928d212d252d23252336939e968d20ad24a520b6915e918d26ad2b1a4e348c7934e209d483a897432e914d2a9a4d348a793ce209d493a8b7436e91cd2b9a4f348e7932e205d48ba887431e912d2a5a4cb489793ae205d49ba8a7435e91ad2b5a4eb48d7936e20dd48ba897433e916d2ada4db48b793ee20dd49ba8b7437e91ed2bda4fb48f7931e20ad213d487a88f430e911d2a3a4c7488f939e203d497a8af434e919d2b3a4e748cf935e20bd184be7ebcba45748af925e23ad25bd4e7a83f426e92dd2dba47748ef92de23bd4ffa80f421e923d2c7a475b1d6ce184fe05feb393ff5cf6d69695ababcc56b69f696ae5ad2b278f992e3bcd58b5b1679cdc734ad58b0a479351efc627c0b0e7e8dc7f8373d78eefcf99b3fee13bdc267f6b465f39b8ef59a57b578cd0bbc239a572d9bbf12776fd0ddd918bd3e3b7dabe3ad5cd2dce2d579cbe8dfb94bc845d3fc211e6e5b495558d9e2ad6c99bba2c55bb0a279a9573fe4ff01285f3937f75f0200", "privateFunctions": [ { "selector": { @@ -43,7 +44,6 @@ exports[`ContractClass creates a contract class from a contract compilation arti "isInternal": false } ], - "packedBytecode": "0x", - "id": "0x034c098fd12d17ec1ecb116e91d01ddc76748569790835f91c866f3e8ec8466a" + "id": "0x09ad0dad993129857629f13ec2f3463d0c15615bd35266d0fb26c8793c6ee050" }" `; diff --git a/yarn-project/circuits.js/src/contract/contract_class.ts b/yarn-project/circuits.js/src/contract/contract_class.ts index 936596d6912..588ead02f09 100644 --- a/yarn-project/circuits.js/src/contract/contract_class.ts +++ b/yarn-project/circuits.js/src/contract/contract_class.ts @@ -4,6 +4,7 @@ import { ContractClass, ContractClassWithId } from '@aztec/types/contracts'; import { getArtifactHash } from './artifact_hash.js'; import { computeContractClassId } from './contract_class_id.js'; +import { packBytecode } from './public_bytecode.js'; /** Contract artifact including its artifact hash */ type ContractArtifactWithHash = ContractArtifact & { artifactHash: Fr }; @@ -13,16 +14,20 @@ export function getContractClassFromArtifact( artifact: ContractArtifact | ContractArtifactWithHash, ): ContractClassWithId { const artifactHash = (artifact as ContractArtifactWithHash).artifactHash ?? getArtifactHash(artifact); + const publicFunctions: ContractClass['publicFunctions'] = artifact.functions + .filter(f => f.functionType === FunctionType.OPEN) + .map(f => ({ + selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters), + bytecode: Buffer.from(f.bytecode, 'base64'), + isInternal: f.isInternal, + })); + const packedBytecode = packBytecode(publicFunctions); + const contractClass: ContractClass = { version: 1, - artifactHash: artifactHash, - publicFunctions: artifact.functions - .filter(f => f.functionType === FunctionType.OPEN) - .map(f => ({ - selector: FunctionSelector.fromNameAndParameters(f.name, f.parameters), - bytecode: Buffer.from(f.bytecode, 'base64'), - isInternal: f.isInternal, - })), + artifactHash, + publicFunctions, + packedBytecode, privateFunctions: artifact.functions .filter(f => f.functionType === FunctionType.SECRET) .map(f => ({ @@ -30,7 +35,6 @@ export function getContractClassFromArtifact( vkHash: getVerificationKeyHash(f.verificationKey!), isInternal: f.isInternal, })), - packedBytecode: Buffer.alloc(0), }; const id = computeContractClassId(contractClass); return { ...contractClass, id }; diff --git a/yarn-project/circuits.js/src/contract/contract_class_id.ts b/yarn-project/circuits.js/src/contract/contract_class_id.ts index d6785002e09..08ad4c3fca5 100644 --- a/yarn-project/circuits.js/src/contract/contract_class_id.ts +++ b/yarn-project/circuits.js/src/contract/contract_class_id.ts @@ -18,11 +18,13 @@ import { computePrivateFunctionsRoot } from './private_function.js'; * @param contractClass - Contract class. * @returns The identifier. */ -export function computeContractClassId(contractClass: ContractClass): Fr { - const { privateFunctionsRoot, publicBytecodeCommitment } = computeContractClassIdPreimage(contractClass); +export function computeContractClassId(contractClass: ContractClass | ContractClassIdPreimage): Fr { + const { artifactHash, privateFunctionsRoot, publicBytecodeCommitment } = isContractClassIdPreimage(contractClass) + ? contractClass + : computeContractClassIdPreimage(contractClass); return Fr.fromBuffer( pedersenHash( - [contractClass.artifactHash.toBuffer(), privateFunctionsRoot.toBuffer(), publicBytecodeCommitment.toBuffer()], + [artifactHash.toBuffer(), privateFunctionsRoot.toBuffer(), publicBytecodeCommitment.toBuffer()], GeneratorIndex.CONTRACT_LEAF, // TODO(@spalladino): Review all generator indices in this file ), ); @@ -31,7 +33,7 @@ export function computeContractClassId(contractClass: ContractClass): Fr { /** Returns the preimage of a contract class id given a contract class. */ export function computeContractClassIdPreimage(contractClass: ContractClass): ContractClassIdPreimage { const privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions); - const publicBytecodeCommitment = computeBytecodeCommitment(contractClass.packedBytecode); + const publicBytecodeCommitment = computePublicBytecodeCommitment(contractClass.packedBytecode); return { artifactHash: contractClass.artifactHash, privateFunctionsRoot, publicBytecodeCommitment }; } @@ -42,7 +44,12 @@ export type ContractClassIdPreimage = { publicBytecodeCommitment: Fr; }; +/** Returns whether the given object looks like a ContractClassIdPreimage. */ +function isContractClassIdPreimage(obj: any): obj is ContractClassIdPreimage { + return obj && obj.artifactHash && obj.privateFunctionsRoot && obj.publicBytecodeCommitment; +} + // TODO(@spalladino): Replace with actual implementation -function computeBytecodeCommitment(bytecode: Buffer) { +export function computePublicBytecodeCommitment(bytecode: Buffer) { return Fr.fromBufferReduce(sha256(bytecode)); } diff --git a/yarn-project/circuits.js/src/contract/contract_class_registered_event.test.ts b/yarn-project/circuits.js/src/contract/contract_class_registered_event.test.ts new file mode 100644 index 00000000000..295f65c40c0 --- /dev/null +++ b/yarn-project/circuits.js/src/contract/contract_class_registered_event.test.ts @@ -0,0 +1,18 @@ +import { getSampleContractClassRegisteredEventPayload } from '../tests/fixtures.js'; +import { computePublicBytecodeCommitment } from './contract_class_id.js'; +import { ContractClassRegisteredEvent } from './contract_class_registered_event.js'; + +describe('ContractClassRegisteredEvent', () => { + it('parses an event as emitted by the ContractClassRegisterer', () => { + const data = getSampleContractClassRegisteredEventPayload(); + const event = ContractClassRegisteredEvent.fromLogData(data); + expect(event.contractClassId.toString()).toEqual( + '0x1c9a43d08a1af21c35e4201262a49497a488b0686209370a70f2434af643b4f7', + ); + expect(event.artifactHash.toString()).toEqual('0x072dce903b1a299d6820eeed695480fe9ec46658b1101885816aed6dd86037f0'); + expect(event.packedPublicBytecode.length).toEqual(27090); + expect(computePublicBytecodeCommitment(event.packedPublicBytecode).toString()).toEqual( + '0x1d5c54998c08cee8ad4a8af5740f2e844fe6db3a5bb4b6382a48b2daeabeee3f', + ); + }); +}); diff --git a/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts b/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts new file mode 100644 index 00000000000..5979413657c --- /dev/null +++ b/yarn-project/circuits.js/src/contract/contract_class_registered_event.ts @@ -0,0 +1,75 @@ +import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader } from '@aztec/foundation/serialize'; +import { ContractClassPublic } from '@aztec/types/contracts'; + +import chunk from 'lodash.chunk'; + +import { CONTRACT_CLASS_REGISTERED_MAGIC_VALUE } from '../constants.gen.js'; +import { computeContractClassId, computePublicBytecodeCommitment } from './contract_class_id.js'; +import { packedBytecodeFromFields, unpackBytecode } from './public_bytecode.js'; + +/** Event emitted from the ContractClassRegisterer. */ +export class ContractClassRegisteredEvent { + constructor( + public readonly contractClassId: Fr, + public readonly version: number, + public readonly artifactHash: Fr, + public readonly privateFunctionsRoot: Fr, + public readonly packedPublicBytecode: Buffer, + ) {} + + static isContractClassRegisteredEvent(log: Buffer) { + return toBigIntBE(log.subarray(0, 32)) == CONTRACT_CLASS_REGISTERED_MAGIC_VALUE; + } + + static fromLogData(log: Buffer) { + if (!this.isContractClassRegisteredEvent(log)) { + const magicValue = CONTRACT_CLASS_REGISTERED_MAGIC_VALUE.toString(16); + throw new Error(`Log data for ContractClassRegisteredEvent is not prefixed with magic value 0x${magicValue}`); + } + const reader = new BufferReader(log.subarray(32)); + const contractClassId = reader.readObject(Fr); + const version = reader.readObject(Fr).toNumber(); + const artifactHash = reader.readObject(Fr); + const privateFunctionsRoot = reader.readObject(Fr); + const packedPublicBytecode = packedBytecodeFromFields( + chunk(reader.readToEnd(), Fr.SIZE_IN_BYTES).map(Buffer.from).map(Fr.fromBuffer), + ); + + return new ContractClassRegisteredEvent( + contractClassId, + version, + artifactHash, + privateFunctionsRoot, + packedPublicBytecode, + ); + } + + toContractClassPublic(): ContractClassPublic { + const computedClassId = computeContractClassId({ + artifactHash: this.artifactHash, + privateFunctionsRoot: this.privateFunctionsRoot, + publicBytecodeCommitment: computePublicBytecodeCommitment(this.packedPublicBytecode), + }); + + if (!computedClassId.equals(this.contractClassId)) { + throw new Error( + `Invalid contract class id: computed ${computedClassId.toString()} but event broadcasted ${this.contractClassId.toString()}`, + ); + } + + if (this.version !== 1) { + throw new Error(`Unexpected contract class version ${this.version}`); + } + + return { + id: this.contractClassId, + artifactHash: this.artifactHash, + packedBytecode: this.packedPublicBytecode, + privateFunctionsRoot: this.privateFunctionsRoot, + publicFunctions: unpackBytecode(this.packedPublicBytecode), + version: this.version, + }; + } +} diff --git a/yarn-project/circuits.js/src/contract/index.ts b/yarn-project/circuits.js/src/contract/index.ts index 71ec200a4bb..5ffe4c961b9 100644 --- a/yarn-project/circuits.js/src/contract/index.ts +++ b/yarn-project/circuits.js/src/contract/index.ts @@ -5,3 +5,5 @@ export * from './contract_class.js'; export * from './artifact_hash.js'; export * from './contract_address.js'; export * from './private_function.js'; +export * from './public_bytecode.js'; +export * from './contract_class_registered_event.js'; diff --git a/yarn-project/circuits.js/src/contract/public_bytecode.test.ts b/yarn-project/circuits.js/src/contract/public_bytecode.test.ts new file mode 100644 index 00000000000..374fe119b1a --- /dev/null +++ b/yarn-project/circuits.js/src/contract/public_bytecode.test.ts @@ -0,0 +1,31 @@ +import { ContractArtifact } from '@aztec/foundation/abi'; + +import { getSampleContractArtifact } from '../tests/fixtures.js'; +import { getContractClassFromArtifact } from './contract_class.js'; +import { packBytecode, packedBytecodeAsFields, packedBytecodeFromFields, unpackBytecode } from './public_bytecode.js'; + +describe('PublicBytecode', () => { + let artifact: ContractArtifact; + beforeAll(() => { + artifact = getSampleContractArtifact(); + }); + + it('packs and unpacks public bytecode', () => { + const { publicFunctions } = getContractClassFromArtifact(artifact); + const packedBytecode = packBytecode(publicFunctions); + const unpackedBytecode = unpackBytecode(packedBytecode); + expect(unpackedBytecode).toEqual(publicFunctions); + }); + + it('converts small packed bytecode back and forth from fields', () => { + const packedBytecode = Buffer.from('1234567890abcdef'.repeat(10), 'hex'); + const fields = packedBytecodeAsFields(packedBytecode); + expect(packedBytecodeFromFields(fields).toString('hex')).toEqual(packedBytecode.toString('hex')); + }); + + it('converts real packed bytecode back and forth from fields', () => { + const { packedBytecode } = getContractClassFromArtifact(artifact); + const fields = packedBytecodeAsFields(packedBytecode); + expect(packedBytecodeFromFields(fields).toString('hex')).toEqual(packedBytecode.toString('hex')); + }); +}); diff --git a/yarn-project/circuits.js/src/contract/public_bytecode.ts b/yarn-project/circuits.js/src/contract/public_bytecode.ts new file mode 100644 index 00000000000..82b6b082884 --- /dev/null +++ b/yarn-project/circuits.js/src/contract/public_bytecode.ts @@ -0,0 +1,74 @@ +import { FunctionSelector } from '@aztec/foundation/abi'; +import { Fr } from '@aztec/foundation/fields'; +import { + BufferReader, + numToInt32BE, + serializeBufferArrayToVector, + serializeToBuffer, +} from '@aztec/foundation/serialize'; +import { ContractClass } from '@aztec/types/contracts'; + +import chunk from 'lodash.chunk'; + +import { FUNCTION_SELECTOR_NUM_BYTES, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS } from '../constants.gen.js'; + +/** + * Packs together a set of public functions for a contract class. + * @remarks This function should no longer be necessary once we have a single bytecode per contract. + */ +export function packBytecode(publicFns: ContractClass['publicFunctions']): Buffer { + return serializeBufferArrayToVector( + publicFns.map(fn => serializeToBuffer(fn.selector, fn.isInternal, numToInt32BE(fn.bytecode.length), fn.bytecode)), + ); +} + +/** + * Unpacks a set of public functions for a contract class from packed bytecode. + * @remarks This function should no longer be necessary once we have a single bytecode per contract. + */ +export function unpackBytecode(buffer: Buffer): ContractClass['publicFunctions'] { + const reader = BufferReader.asReader(buffer); + return reader.readVector({ + fromBuffer: (reader: BufferReader) => ({ + selector: FunctionSelector.fromBuffer(reader.readBytes(FUNCTION_SELECTOR_NUM_BYTES)), + isInternal: reader.readBoolean(), + bytecode: reader.readBuffer(), + }), + }); +} + +/** + * Formats packed bytecode as an array of fields. Splits the input into 31-byte chunks, and stores each + * of them into a field, omitting the field's first byte, then adds zero-fields at the end until the max length. + * @param packedBytecode - Packed bytecode for a contract. + * @returns A field with the total length in bytes, followed by an array of fields such that their concatenation is equal to the input buffer. + * @remarks This function is more generic than just for packed bytecode, perhaps it could be moved elsewhere. + */ +export function packedBytecodeAsFields(packedBytecode: Buffer): Fr[] { + const encoded = [ + new Fr(packedBytecode.length), + ...chunk(packedBytecode, Fr.SIZE_IN_BYTES - 1).map(c => { + const fieldBytes = Buffer.alloc(32); + Buffer.from(c).copy(fieldBytes, 1); + return Fr.fromBuffer(fieldBytes); + }), + ]; + if (encoded.length > MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS) { + throw new Error( + `Packed bytecode exceeds maximum size: got ${encoded.length} but max is ${MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS}`, + ); + } + // Fun fact: we cannot use padArrayEnd here since typescript cannot deal with a Tuple this big + return [...encoded, ...Array(MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - encoded.length).fill(Fr.ZERO)]; +} + +/** + * Recovers packed bytecode from an array of fields. + * @param fields - An output from packedBytecodeAsFields. + * @returns The packed bytecode. + * @remarks This function is more generic than just for packed bytecode, perhaps it could be moved elsewhere. + */ +export function packedBytecodeFromFields(fields: Fr[]): Buffer { + const [length, ...payload] = fields; + return Buffer.concat(payload.map(f => f.toBuffer().subarray(1))).subarray(0, length.toNumber()); +} diff --git a/yarn-project/circuits.js/src/scripts/constants.in.ts b/yarn-project/circuits.js/src/scripts/constants.in.ts index 14892b1e1fe..6a1dcd6312a 100644 --- a/yarn-project/circuits.js/src/scripts/constants.in.ts +++ b/yarn-project/circuits.js/src/scripts/constants.in.ts @@ -14,7 +14,7 @@ interface ParsedContent { /** * Constants. */ - constants: { [key: string]: number }; + constants: { [key: string]: string }; /** * GeneratorIndexEnum. */ @@ -27,10 +27,10 @@ interface ParsedContent { * @param constants - An object containing key-value pairs representing constants. * @returns A string containing code that exports the constants as TypeScript constants. */ -function processConstantsTS(constants: { [key: string]: number }): string { +function processConstantsTS(constants: { [key: string]: string }): string { const code: string[] = []; Object.entries(constants).forEach(([key, value]) => { - code.push(`export const ${key} = ${value};`); + code.push(`export const ${key} = ${+value > Number.MAX_SAFE_INTEGER ? value + 'n' : value};`); }); return code.join('\n'); } @@ -63,7 +63,7 @@ function processEnumTS(enumName: string, enumValues: { [key: string]: number }): * @param prefix - A prefix to add to the constant names. * @returns A string containing code that exports the constants as Noir constants. */ -function processConstantsSolidity(constants: { [key: string]: number }, prefix = ''): string { +function processConstantsSolidity(constants: { [key: string]: string }, prefix = ''): string { const code: string[] = []; Object.entries(constants).forEach(([key, value]) => { code.push(` uint256 internal constant ${prefix}${key} = ${value};`); @@ -114,7 +114,7 @@ ${processConstantsSolidity(constants)} * Parse the content of the constants file in Noir. */ function parseNoirFile(fileContent: string): ParsedContent { - const constants: { [key: string]: number } = {}; + const constants: { [key: string]: string } = {}; const generatorIndexEnum: { [key: string]: number } = {}; fileContent.split('\n').forEach(l => { @@ -123,7 +123,7 @@ function parseNoirFile(fileContent: string): ParsedContent { return; } - const [, name, _type, value] = line.match(/global\s+(\w+)(\s*:\s*\w+)?\s*=\s*(\d+);/) || []; + const [, name, _type, value] = line.match(/global\s+(\w+)(\s*:\s*\w+)?\s*=\s*(0x[a-fA-F0-9]+|\d+);/) || []; if (!name || !value) { // eslint-disable-next-line no-console console.warn(`Unknown content: ${line}`); @@ -134,7 +134,7 @@ function parseNoirFile(fileContent: string): ParsedContent { if (indexName) { generatorIndexEnum[indexName] = +value; } else { - constants[name] = +value; + constants[name] = value; } }); diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 3b76b5d768a..55dc763ec2b 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -3,6 +3,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { EthAddress } from '@aztec/foundation/eth-address'; import { numToUInt32BE } from '@aztec/foundation/serialize'; +import { ContractClassPublic, PrivateFunction, PublicFunction } from '@aztec/types/contracts'; import { SchnorrSignature } from '../barretenberg/index.js'; import { @@ -104,6 +105,9 @@ import { VK_TREE_HEIGHT, VerificationKey, WitnessedPublicCallData, + computeContractClassId, + computePublicBytecodeCommitment, + packBytecode, } from '../index.js'; import { GlobalVariables } from '../structs/global_variables.js'; import { Header, NUM_BYTES_PER_SHA256 } from '../structs/header.js'; @@ -1072,6 +1076,40 @@ export function makeBaseRollupInputs(seed = 0): BaseRollupInputs { }); } +export function makeContractClassPublic(seed = 0): ContractClassPublic { + const artifactHash = fr(seed + 1); + const publicFunctions = makeTuple(3, makeContractClassPublicFunction, seed + 2); + const privateFunctionsRoot = fr(seed + 3); + const packedBytecode = packBytecode(publicFunctions); + const publicBytecodeCommitment = computePublicBytecodeCommitment(packedBytecode); + const id = computeContractClassId({ artifactHash, privateFunctionsRoot, publicBytecodeCommitment }); + return { + id, + artifactHash, + packedBytecode, + privateFunctionsRoot, + publicFunctions, + version: 1, + }; +} + +function makeContractClassPublicFunction(seed = 0): PublicFunction { + return { + selector: FunctionSelector.fromField(fr(seed + 1)), + bytecode: makeBytes(100, seed + 2), + isInternal: false, + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function makeContractClassPrivateFunction(seed = 0): PrivateFunction { + return { + selector: FunctionSelector.fromField(fr(seed + 1)), + vkHash: fr(seed + 2), + isInternal: false, + }; +} + /** * TODO: Since the max value check is currently disabled this function is pointless. Should it be removed? * Test only. Easy to identify big endian field serialize. diff --git a/yarn-project/circuits.js/src/tests/fixtures.ts b/yarn-project/circuits.js/src/tests/fixtures.ts index 135bd6edbe5..c65e4787b39 100644 --- a/yarn-project/circuits.js/src/tests/fixtures.ts +++ b/yarn-project/circuits.js/src/tests/fixtures.ts @@ -6,8 +6,19 @@ import { readFileSync } from 'fs'; import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; +// Copied from the build output for the contract `Benchmarking` in noir-contracts export function getSampleContractArtifact(): ContractArtifact { - const path = resolve(dirname(fileURLToPath(import.meta.url)), '../../fixtures/Benchmarking.test.json'); + const path = getPathToFixture('Benchmarking.test.json'); const content = JSON.parse(readFileSync(path).toString()) as NoirCompiledContract; return loadContractArtifact(content); } + +// Copied from the test 'registers a new contract class' in end-to-end/src/e2e_deploy_contract.test.ts +export function getSampleContractClassRegisteredEventPayload(): Buffer { + const path = getPathToFixture('ContractClassRegisteredEventData.hex'); + return Buffer.from(readFileSync(path).toString(), 'hex'); +} + +function getPathToFixture(name: string) { + return resolve(dirname(fileURLToPath(import.meta.url)), `../../fixtures/${name}`); +} diff --git a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts index c03353a9a88..1918d32c195 100644 --- a/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts +++ b/yarn-project/end-to-end/src/cli_docs_sandbox.test.ts @@ -99,6 +99,7 @@ Rollup Address: 0x0dcd1bf9a1b36ce34237eeafef220932846bcd82 BenchmarkingContractArtifact CardGameContractArtifact ChildContractArtifact +ContractClassRegistererContractArtifact CounterContractArtifact DocsExampleContractArtifact EasyPrivateTokenContractArtifact diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts index 83023b8505a..983eff7e4bb 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts @@ -1,9 +1,11 @@ import { AztecAddress, + AztecNode, BatchCall, CompleteAddress, Contract, ContractArtifact, + ContractBase, ContractDeployer, DebugLogger, EthAddress, @@ -12,11 +14,17 @@ import { SignerlessWallet, TxStatus, Wallet, + getContractClassFromArtifact, getContractInstanceFromDeployParams, isContractDeployed, } from '@aztec/aztec.js'; +import { + computePrivateFunctionsRoot, + computePublicBytecodeCommitment, + packedBytecodeAsFields, +} from '@aztec/circuits.js'; import { siloNullifier } from '@aztec/circuits.js/abis'; -import { StatefulTestContract } from '@aztec/noir-contracts'; +import { ContractClassRegistererContract, ReaderContractArtifact, StatefulTestContract } from '@aztec/noir-contracts'; import { TestContract, TestContractArtifact } from '@aztec/noir-contracts/Test'; import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; import { SequencerClient } from '@aztec/sequencer-client'; @@ -29,10 +37,11 @@ describe('e2e_deploy_contract', () => { let logger: DebugLogger; let wallet: Wallet; let sequencer: SequencerClient | undefined; + let aztecNode: AztecNode; let teardown: () => Promise; beforeEach(async () => { - ({ teardown, pxe, accounts, logger, wallet, sequencer } = await setup()); + ({ teardown, pxe, accounts, logger, wallet, sequencer, aztecNode } = await setup()); }, 100_000); afterEach(() => teardown()); @@ -240,6 +249,42 @@ describe('e2e_deploy_contract', () => { expect(await contracts[0].methods.summed_values(owner).view()).toEqual(42n); expect(await contracts[1].methods.summed_values(owner).view()).toEqual(52n); }); + + // Tests registering a new contract class on a node + // All this dance will be hidden behind a nicer API in the near future! + it('registers a new contract class via the class registerer contract', async () => { + const registerer = await registerContract(wallet, ContractClassRegistererContract, [], new Fr(1)); + const contractClass = getContractClassFromArtifact(ReaderContractArtifact); + const privateFunctionsRoot = computePrivateFunctionsRoot(contractClass.privateFunctions); + const publicBytecodeCommitment = computePublicBytecodeCommitment(contractClass.packedBytecode); + + logger(`contractClass.id: ${contractClass.id}`); + logger(`contractClass.artifactHash: ${contractClass.artifactHash}`); + logger(`contractClass.privateFunctionsRoot: ${privateFunctionsRoot}`); + logger(`contractClass.publicBytecodeCommitment: ${publicBytecodeCommitment}`); + logger(`contractClass.packedBytecode.length: ${contractClass.packedBytecode.length}`); + + const tx = await registerer.methods + .register( + contractClass.artifactHash, + privateFunctionsRoot, + publicBytecodeCommitment, + packedBytecodeAsFields(contractClass.packedBytecode), + ) + .send() + .wait(); + + const logs = await pxe.getUnencryptedLogs({ txHash: tx.txHash }); + const registeredLog = logs.logs[0].log; // We need a nicer API! + expect(registeredLog.contractAddress).toEqual(registerer.address); + + const registeredClass = await aztecNode.getContractClass(contractClass.id); + expect(registeredClass).toBeDefined(); + expect(registeredClass?.artifactHash.toString()).toEqual(contractClass.artifactHash.toString()); + expect(registeredClass?.privateFunctionsRoot.toString()).toEqual(privateFunctionsRoot.toString()); + expect(registeredClass?.packedBytecode.toString('hex')).toEqual(contractClass.packedBytecode.toString('hex')); + expect(registeredClass?.publicFunctions).toEqual(contractClass.publicFunctions); + }); }); type StatefulContractCtorArgs = Parameters; @@ -250,13 +295,18 @@ async function registerRandomAccount(pxe: PXE): Promise { return owner.address; } -type ContractArtifactClass = { - at(address: AztecAddress, wallet: Wallet): Promise; +type ContractArtifactClass = { + at(address: AztecAddress, wallet: Wallet): Promise; artifact: ContractArtifact; }; -async function registerContract(wallet: Wallet, contractArtifact: ContractArtifactClass, args: any[] = []) { - const instance = getContractInstanceFromDeployParams(contractArtifact.artifact, args); +async function registerContract( + wallet: Wallet, + contractArtifact: ContractArtifactClass, + args: any[] = [], + salt?: Fr, +): Promise { + const instance = getContractInstanceFromDeployParams(contractArtifact.artifact, args, salt); await wallet.addContracts([{ artifact: contractArtifact.artifact, instance }]); return contractArtifact.at(instance.address, wallet); } diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 0e622cd4fde..0aebb8fa3cc 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -51,7 +51,7 @@ abstract class BaseField { } else if (typeof value === 'bigint' || typeof value === 'number' || typeof value === 'boolean') { this.asBigInt = BigInt(value); if (this.asBigInt >= this.modulus()) { - throw new Error('Value >= to field modulus.'); + throw new Error(`Value 0x${this.asBigInt.toString(16)} is greater or equal to field modulus.`); } } else if (value instanceof BaseField) { this.asBuffer = value.asBuffer; @@ -89,12 +89,20 @@ abstract class BaseField { if (this.asBigInt === undefined) { this.asBigInt = toBigIntBE(this.asBuffer!); if (this.asBigInt >= this.modulus()) { - throw new Error('Value >= to field modulus.'); + throw new Error(`Value 0x${this.asBigInt.toString(16)} is greater or equal to field modulus.`); } } return this.asBigInt; } + toNumber(): number { + const value = this.toBigInt(); + if (value > Number.MAX_SAFE_INTEGER) { + throw new Error(`Value ${value.toString(16)} greater than than max safe integer`); + } + return Number(value); + } + toShortString(): string { const str = this.toString(); return `${str.slice(0, 10)}...${str.slice(-4)}`; diff --git a/yarn-project/foundation/src/serialize/buffer_reader.ts b/yarn-project/foundation/src/serialize/buffer_reader.ts index d11efff3cb5..48c0c553c59 100644 --- a/yarn-project/foundation/src/serialize/buffer_reader.ts +++ b/yarn-project/foundation/src/serialize/buffer_reader.ts @@ -111,6 +111,13 @@ export class BufferReader { return Buffer.from(this.buffer.subarray(this.index - n, this.index)); } + /** Reads until the end of the buffer. */ + public readToEnd(): Buffer { + const result = this.buffer.subarray(this.index); + this.index = this.buffer.length; + return result; + } + /** * Reads a vector of numbers from the buffer and returns it as an array of numbers. * The method utilizes the 'readVector' method, passing a deserializer that reads numbers. diff --git a/yarn-project/foundation/src/types/index.ts b/yarn-project/foundation/src/types/index.ts index 80c6fcbfe5a..ff34c215611 100644 --- a/yarn-project/foundation/src/types/index.ts +++ b/yarn-project/foundation/src/types/index.ts @@ -1,7 +1,8 @@ -/** - * Strips methods of a type. - */ +/** Strips methods of a type. */ export type FieldsOf = { // eslint-disable-next-line @typescript-eslint/ban-types [P in keyof T as T[P] extends Function ? never : P]: T[P]; }; + +/** Marks a set of properties of a type as optional. */ +export type PartialBy = Omit & Partial>; diff --git a/yarn-project/noir-contracts/Nargo.toml b/yarn-project/noir-contracts/Nargo.toml index 4c919e77911..d5db8034622 100644 --- a/yarn-project/noir-contracts/Nargo.toml +++ b/yarn-project/noir-contracts/Nargo.toml @@ -4,6 +4,7 @@ members = [ "contracts/benchmarking_contract", "contracts/card_game_contract", "contracts/child_contract", + "contracts/contract_class_registerer_contract", "contracts/counter_contract", "contracts/docs_example_contract", "contracts/easy_private_token_contract", diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/Nargo.toml b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/Nargo.toml new file mode 100644 index 00000000000..f500e459537 --- /dev/null +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "contract_class_registerer_contract" +authors = [""] +compiler_version = ">=0.18.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } diff --git a/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr new file mode 100644 index 00000000000..fb45783033a --- /dev/null +++ b/yarn-project/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -0,0 +1,67 @@ +contract ContractClassRegisterer { + use dep::std::option::Option; + use dep::aztec::protocol_types::{ + address::{ AztecAddress, EthAddress }, + contract_class::ContractClassId, + constants::{MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, CONTRACT_CLASS_REGISTERED_MAGIC_VALUE} + }; + + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/3590): Remove this once the issue is fixed + use dep::aztec::protocol_types; + + use dep::aztec::log::{ emit_unencrypted_log, emit_unencrypted_log_from_private}; + + #[event] + struct ContractClassRegistered { + contract_class_id: ContractClassId, + version: Field, + artifact_hash: Field, + private_functions_root: Field, + packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS], + } + + impl ContractClassRegistered { + fn serialize(self: Self) -> [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS + 5] { + let mut packed = [0; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS + 5]; + packed[0] = CONTRACT_CLASS_REGISTERED_MAGIC_VALUE; + packed[1] = self.contract_class_id.to_field(); + packed[2] = self.version; + packed[3] = self.artifact_hash; + packed[4] = self.private_functions_root; + for i in 0..MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS { + packed[i + 5] = self.packed_public_bytecode[i]; + } + packed + } + } + + #[aztec(private)] + fn constructor() {} + + #[aztec(private)] + fn register( + artifact_hash: Field, + private_functions_root: Field, + public_bytecode_commitment: Field, + packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] + ) { + // TODO: Validate public_bytecode_commitment is the correct commitment of packed_public_bytecode + // TODO: Validate packed_public_bytecode is legit public bytecode + + // Compute contract class id from preimage + let contract_class_id = ContractClassId::compute( + artifact_hash, + private_functions_root, + public_bytecode_commitment + ); + + // Emit the contract class id as a nullifier to be able to prove that this class has been (not) registered + let event = ContractClassRegistered { contract_class_id, version: 1, artifact_hash, private_functions_root, packed_public_bytecode }; + context.push_new_nullifier(contract_class_id.to_field(), 0); + + // Broadcast class info including public bytecode + let event_payload = event.serialize(); + dep::aztec::oracle::debug_log::debug_log_array_with_prefix("ContractClassRegistered", event_payload); + emit_unencrypted_log_from_private(&mut context, event_payload); + } +} diff --git a/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap b/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap index 0b402d4b977..b202c7aceee 100644 --- a/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap +++ b/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap @@ -14,6 +14,49 @@ exports[`Noir compatibility tests (interop_testing.nr) Public key hash matches N exports[`Noir compatibility tests (interop_testing.nr) TxRequest Hash matches Noir 1`] = `"0x0b487ff2900ae1178e131bfe333fdbc351beef658f7c0d62db2801429b1aab75"`; +exports[`Noir compatibility tests (interop_testing.nr) Var args hash matches noir 1`] = ` +Fr { + "asBigInt": 1557627899280963684159398665725097926236612957540256425197580046184563077271n, + "asBuffer": { + "data": [ + 3, + 113, + 150, + 13, + 216, + 78, + 211, + 68, + 90, + 176, + 153, + 172, + 76, + 26, + 245, + 186, + 144, + 224, + 199, + 19, + 181, + 147, + 224, + 202, + 82, + 238, + 83, + 32, + 135, + 199, + 240, + 151, + ], + "type": "Buffer", + }, +} +`; + exports[`Private kernel Executes private kernel init circuit for a contract deployment 1`] = ` KernelCircuitPublicInputs { "constants": CombinedConstantData { diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr index df1892736a2..54b74a8cd7d 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr @@ -81,7 +81,16 @@ global MAPPING_SLOT_PEDERSEN_SEPARATOR: Field = 4; // sha256 hash is stored in two fields to accommodate all 256-bits of the hash global NUM_FIELDS_PER_SHA256: Field = 2; global ARGS_HASH_CHUNK_LENGTH: u32 = 32; -global ARGS_HASH_CHUNK_COUNT: u32 = 16; +global ARGS_HASH_CHUNK_COUNT: u32 = 32; +// This should be around 8192 (assuming 2**15 instructions packed at 8 bytes each), +// but it's reduced to speed up build times, otherwise the ClassRegisterer takes over 5 mins to compile. +// We are not using 1024 so we can squeeze in a few more args to methods that consume packed public bytecode, +// such as the ClassRegisterer.register, and still land below the 32 * 32 max args limit for hashing. +global MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS: Field = 1000; +// Since we are not yet emitting selectors we'll use this magic value to identify events emitted by the ClassRegisterer. +// This is just a stopgap until we implement proper selectors. +// This is the sha224sum of 'struct ContractClassRegistered {contract_class_id: ContractClassId, version: Field, artifact_hash: Field, private_functions_root: Field, packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] }' +global CONTRACT_CLASS_REGISTERED_MAGIC_VALUE = 0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8; // NOIR CONSTANTS - constants used only in yarn-packages/noir-contracts // Some are defined here because Noir doesn't yet support globals referencing other globals yet. diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/interop_testing.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/interop_testing.nr index e5d28d3eb48..0e1624e75c3 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/interop_testing.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/interop_testing.nr @@ -6,7 +6,7 @@ use crate::abis::function_data::FunctionData; use crate::abis::function_leaf_preimage::FunctionLeafPreimage; use crate::contrakt::deployment_data::ContractDeploymentData; use crate::abis::function_selector::FunctionSelector; -use crate::hash::{compute_l2_to_l1_hash, sha256_to_field}; +use crate::hash::{compute_l2_to_l1_hash, sha256_to_field, hash_args}; use crate::abis::call_stack_item::PublicCallStackItem; use crate::abis::public_circuit_public_inputs::PublicCircuitPublicInputs; use crate::abis::side_effect::SideEffect; @@ -47,6 +47,16 @@ fn compute_address_from_partial_and_pubkey() { assert(address.to_field() == 0x0447f893197175723deb223696e2e96dbba1e707ee8507766373558877e74197); } +#[test] +fn compute_var_args_hash() { + let mut input = [0; 800]; + for i in 0..800 { + input[i] = i as Field; + } + let hash = hash_args(input); + assert(hash == 1557627899280963684159398665725097926236612957540256425197580046184563077271); +} + #[test] fn compute_tx_request_hash() { let tx_request = TxRequest { diff --git a/yarn-project/noir-protocol-circuits/src/index.test.ts b/yarn-project/noir-protocol-circuits/src/index.test.ts index 2afc9ce12c4..b9ac1fe31bd 100644 --- a/yarn-project/noir-protocol-circuits/src/index.test.ts +++ b/yarn-project/noir-protocol-circuits/src/index.test.ts @@ -17,7 +17,8 @@ import { computeContractAddressFromPartial, computePublicKeysHash, } from '@aztec/circuits.js'; -import { computeTxHash } from '@aztec/circuits.js/abis'; +import { computeTxHash, computeVarArgsHash } from '@aztec/circuits.js/abis'; +import { times } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { fileURLToPath } from '@aztec/foundation/url'; @@ -181,6 +182,12 @@ describe('Noir compatibility tests (interop_testing.nr)', () => { const publicCallStackItem = new PublicCallStackItem(contractAddress, functionData, appPublicInputs, true); expect(publicCallStackItem.hash().toString()).toMatchSnapshot(); }); + + it('Var args hash matches noir', () => { + const args = times(800, i => new Fr(i)); + const res = computeVarArgsHash(args); + expect(res).toMatchSnapshot(); + }); }); function numberToBuffer(value: number) { diff --git a/yarn-project/types/src/contracts/contract_class.test.ts b/yarn-project/types/src/contracts/contract_class.test.ts deleted file mode 100644 index 8521217473c..00000000000 --- a/yarn-project/types/src/contracts/contract_class.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SerializableContractClass } from './contract_class.js'; - -describe('ContractClass', () => { - it('can serialize and deserialize a contract class', () => { - const contractClass = SerializableContractClass.random(); - expect(SerializableContractClass.fromBuffer(contractClass.toBuffer())).toEqual(contractClass); - }); -}); diff --git a/yarn-project/types/src/contracts/contract_class.ts b/yarn-project/types/src/contracts/contract_class.ts index b8444e166df..c575485ad33 100644 --- a/yarn-project/types/src/contracts/contract_class.ts +++ b/yarn-project/types/src/contracts/contract_class.ts @@ -1,10 +1,14 @@ import { FunctionSelector } from '@aztec/foundation/abi'; -import { randomBytes } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize'; +import { PartialBy } from '@aztec/foundation/types'; const VERSION = 1 as const; +/** + * A Contract Class in the protocol. Aztec differentiates contracts classes and instances, where a + * contract class represents the code of the contract, but holds no state. Classes are identified by + * an id that is a commitment to all its data. + */ export interface ContractClass { /** Version of the contract class. */ version: typeof VERSION; @@ -18,66 +22,7 @@ export interface ContractClass { packedBytecode: Buffer; } -/** Serializable implementation of the contract class interface. */ -export class SerializableContractClass implements ContractClass { - /** Version identifier. Initially one, bumped for any changes to the contract class struct. */ - public readonly version = VERSION; - - public readonly artifactHash: Fr; - public readonly packedBytecode: Buffer; - public readonly privateFunctions: SerializablePrivateFunction[]; - public readonly publicFunctions: SerializablePublicFunction[]; - - constructor(contractClass: ContractClass) { - if (contractClass.version !== VERSION) { - throw new Error(`Unexpected contract class version ${contractClass.version}`); - } - this.privateFunctions = contractClass.privateFunctions.map(x => new SerializablePrivateFunction(x)); - this.publicFunctions = contractClass.publicFunctions.map(x => new SerializablePublicFunction(x)); - this.artifactHash = contractClass.artifactHash; - this.packedBytecode = contractClass.packedBytecode; - } - - /** Returns a copy of this object with its id included. */ - withId(id: Fr): ContractClassWithId { - return { ...this, id }; - } - - public toBuffer() { - return serializeToBuffer( - numToUInt8(this.version), - this.artifactHash, - this.privateFunctions.length, - this.privateFunctions, - this.publicFunctions.length, - this.publicFunctions, - this.packedBytecode.length, - this.packedBytecode, - ); - } - - static fromBuffer(bufferOrReader: BufferReader | Buffer) { - const reader = BufferReader.asReader(bufferOrReader); - return new SerializableContractClass({ - version: reader.readUInt8() as typeof VERSION, - artifactHash: reader.readObject(Fr), - privateFunctions: reader.readVector(SerializablePrivateFunction), - publicFunctions: reader.readVector(SerializablePublicFunction), - packedBytecode: reader.readBuffer(), - }); - } - - static random() { - return new SerializableContractClass({ - version: VERSION, - artifactHash: Fr.random(), - privateFunctions: [SerializablePrivateFunction.random()], - publicFunctions: [SerializablePublicFunction.random()], - packedBytecode: randomBytes(32), - }); - } -} - +/** Private function definition within a contract class. */ export interface PrivateFunction { /** Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. */ selector: FunctionSelector; @@ -90,40 +35,7 @@ export interface PrivateFunction { isInternal: boolean; } -/** Private function in a Contract Class. */ -export class SerializablePrivateFunction { - public readonly selector: FunctionSelector; - public readonly vkHash: Fr; - public readonly isInternal: boolean; - - constructor(privateFunction: PrivateFunction) { - this.selector = privateFunction.selector; - this.vkHash = privateFunction.vkHash; - this.isInternal = privateFunction.isInternal; - } - - public toBuffer() { - return serializeToBuffer(this.selector, this.vkHash, this.isInternal); - } - - static fromBuffer(bufferOrReader: BufferReader | Buffer): PrivateFunction { - const reader = BufferReader.asReader(bufferOrReader); - return new SerializablePrivateFunction({ - selector: reader.readObject(FunctionSelector), - vkHash: reader.readObject(Fr), - isInternal: reader.readBoolean(), - }); - } - - static random() { - return new SerializablePrivateFunction({ - selector: FunctionSelector.random(), - vkHash: Fr.random(), - isInternal: false, - }); - } -} - +/** Public function definition within a contract class. */ export interface PublicFunction { /** Selector of the function. Calculated as the hash of the method name and parameters. The specification of this is not enforced by the protocol. */ selector: FunctionSelector; @@ -136,40 +48,19 @@ export interface PublicFunction { isInternal: boolean; } -/** - * Public function in a Contract Class. Use `packedBytecode` in the parent class once supported. - */ -export class SerializablePublicFunction { - public readonly selector: FunctionSelector; - public readonly bytecode: Buffer; - public readonly isInternal: boolean; - - constructor(publicFunction: PublicFunction) { - this.selector = publicFunction.selector; - this.bytecode = publicFunction.bytecode; - this.isInternal = publicFunction.isInternal; - } - - public toBuffer() { - return serializeToBuffer(this.selector, this.bytecode.length, this.bytecode, this.isInternal); - } - - static fromBuffer(bufferOrReader: BufferReader | Buffer): PublicFunction { - const reader = BufferReader.asReader(bufferOrReader); - return new SerializablePublicFunction({ - selector: reader.readObject(FunctionSelector), - bytecode: reader.readBuffer(), - isInternal: reader.readBoolean(), - }); - } - - static random() { - return new SerializablePublicFunction({ - selector: FunctionSelector.random(), - bytecode: randomBytes(32), - isInternal: false, - }); - } +/** Commitments to fields of a contract class. */ +interface ContractClassCommitments { + /** Identifier of the contract class. */ + id: Fr; + /** Commitment to the public bytecode. */ + publicBytecodeCommitment: Fr; + /** Root of the private functions tree */ + privateFunctionsRoot: Fr; } -export type ContractClassWithId = ContractClass & { id: Fr }; +/** A contract class with its precomputed id. */ +export type ContractClassWithId = ContractClass & Pick; + +/** A contract class with public bytecode information only. */ +export type ContractClassPublic = PartialBy & + Pick;