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;