diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a69da686a0..01726da680b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -892,16 +892,6 @@ jobs: aztec_manifest_key: end-to-end <<: *defaults_e2e_test - integration-archiver-l1-to-l2: - steps: - - *checkout - - *setup_env - - run: - name: "Test" - command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=integration_archiver_l1_to_l2.test.ts - aztec_manifest_key: end-to-end - <<: *defaults_e2e_test - integration-l1-publisher: steps: - *checkout @@ -1421,7 +1411,6 @@ workflows: - e2e-private-voting: *e2e_test - uniswap-trade-on-l1-from-l2: *e2e_test - integration-l1-publisher: *e2e_test - - integration-archiver-l1-to-l2: *e2e_test - e2e-persistence: *e2e_test - e2e-browser: *e2e_test - e2e-card-game: *e2e_test @@ -1486,7 +1475,6 @@ workflows: - e2e-private-voting - uniswap-trade-on-l1-from-l2 - integration-l1-publisher - - integration-archiver-l1-to-l2 - e2e-persistence - e2e-browser - e2e-card-game diff --git a/docs/docs/developers/contracts/references/portals/data_structures.md b/docs/docs/developers/contracts/references/portals/data_structures.md index 96ead1a06c0..babbe4211d0 100644 --- a/docs/docs/developers/contracts/references/portals/data_structures.md +++ b/docs/docs/developers/contracts/references/portals/data_structures.md @@ -14,10 +14,8 @@ An entry for the messageboxes multi-sets. | Name | Type | Description | | -------------- | ------- | ----------- | -| `fee` | `uint64` | The fee provided to the sequencer for including the message in the inbox. 0 if Outbox (as it is not applicable). | | `count` | `uint32` | The occurrence of the entry in the dataset | | `version` | `uint32` | The version of the entry | -| `deadline` | `uint32` | The consumption deadline of the message. | ## `L1Actor` @@ -55,8 +53,6 @@ A message that is sent from L1 to L2. | `recipient` | `L2Actor` | The actor on L2 that is to receive the message. | | `content` | `field (~254 bits)` | The field element containing the content to be sent to L2. | | `secretHash` | `field (~254 bits)` | The hash of a secret pre-image that must be known to consume the message on L2. Use the [`computeMessageSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/secrets.ts) to compute it from a secret. | -| `deadline` | `uint32` | The message consumption-deadline time in seconds. | -| `fee` | `uint64` | The fee that the sequencer will be paid for the inclusion of the message. | ## `L2ToL1Message` diff --git a/docs/docs/developers/contracts/references/portals/inbox.md b/docs/docs/developers/contracts/references/portals/inbox.md index ea8a3609830..73b18fc8888 100644 --- a/docs/docs/developers/contracts/references/portals/inbox.md +++ b/docs/docs/developers/contracts/references/portals/inbox.md @@ -16,98 +16,26 @@ Sends a message from L1 to L2. | Name | Type | Description | | -------------- | ------- | ----------- | | Recipient | `L2Actor` | The recipient of the message. This **MUST** match the rollup version and an Aztec contract that is **attached** to the contract making this call. If the recipient is not attached to the caller, the message cannot be consumed by it. | -| Deadline | `uint256` | The message consumption deadline. If the message have not been removed from the `Inbox` and included in a rollup block by this point, it can be *canceled* by the portal (the portal must implement logic to cancel). | | Content | `field` (~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to be a single field for rollup purposes. If the content is small enough it can just be passed along, otherwise it should be hashed and the hash passed along (you can use our [`Hash`](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Hash.sol) utilities with `sha256ToField` functions) | | Secret Hash | `field` (~254 bits) | A hash of a secret that is used when consuming the message on L2. Keep this preimage a secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed) - so make sure your app keeps track of the pre-images! Use the [`computeMessageSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/secrets.ts) to compute it from a secret. | -| Fee (msg.value) | `uint256` | The fee to the sequencer for including the message. This is the amount of ETH that the sequencer will receive for including the message. Note that only values that can fit in `uint64` will be accepted | | ReturnValue | `bytes32` | The message hash, used as an identifier | #### Edge cases - Will revert with `Inbox__ActorTooLarge(bytes32 actor)` if the recipient is larger than the field size (~254 bits). -- Will revert with `Inbox__DeadlineBeforeNow()` if the deadline is before the current block. - Will revert with `Inbox__ContentTooLarge(bytes32 content)` if the content is larger than the field size (~254 bits). - Will revert with `Inbox__SecretHashTooLarge(bytes32 secretHash)` if the secret hash is larger than the field size (~254 bits). -- Will revert with `Inbox__FeeTooHigh()` if the fee is larger than `type(uint64).max`. -- Will revert `Inbox__IncompatibleEntryArguments(bytes32 entryKey, uint64 storedFee, uint64 feePassed, uint32 storedVersion, uint32 versionPassed, uint32 storedDeadline, uint32 deadlinePassed)` if insertion is not possible due to invalid entry arguments. -## `cancelL2Message()` -Cancels a message that has not yet been consumed. - -#include_code pending_l2_cancel l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -| Name | Type | Description | -| -------------- | ------- | ----------- | -| `_message` | `L1ToL2Msg` | The message to cancel | -| `_feeCollector`| `address` | The address to refund the fee to | -| ReturnValue | `bytes32` | The hash of the message | - -#### Edge cases - -- Will revert with `Inbox__Unauthorized()` if `msg.sender != _message.sender.actor`. -- Will revert with `Inbox__NotPastDeadline()` if `block.timestamp <= _message.deadline`. -- Will revert with `Inbox__NothingToConsume(bytes32 entryKey)` if the message does not exist. - -## `batchConsume()` +## `consume()` Allows the `Rollup` to consume multiple messages in a single transaction. -#include_code inbox_batch_consume l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -| Name | Type | Description | -| -------------- | ------- | ----------- | -| `_entryKeys` | `bytes32[]` | The entry keys (message hashes) to consume | -| ReturnValue | `Entry` | The entry for the given key | - -#### Edge cases - -- Will revert with `Registry__RollupNotRegistered(address rollup)` if `msg.sender` is not registered as a rollup on the [`Registry`](./registry.md). -- Will revert with `Inbox__InvalidVersion(uint256 entry, uint256 rollup)` if the rollup version does not match the version specified in the message. -- Will revert with `Inbox__PastDeadline()` if the message deadline has passed. -- Will revert with `Inbox__NothingToConsume(bytes32 entryKey)` if the message does not exist. - -## `withdrawFees()` - -Will claim the fees that has accrued to the `msg.sender` from consuming messages. - -Let the sequencer withdraw fees from the inbox. +#include_code consume l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity -#include_code inbox_withdraw_fees l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity +| Name | Type | Description | +| -------------- | ----------- | -------------------------- | +| ReturnValue | `bytes32` | Root of the consumed tree. | #### Edge cases -- Will revert with `Inbox__FailedToWithdrawFees()` if the transfer call fails. - -## `get()` -Retrieves the `entry` for a given message. The entry contains fee, number of occurrences, deadline and version information. - -#include_code inbox_get l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -| Name | Type | Description | -| -------------- | ------- | ----------- | -| `_entryKey` | `bytes32` | The entry key (message hash) | -| ReturnValue | `Entry` | The entry object for the given key | - -#### Edge cases -- Will revert with `Inbox__NothingToConsume(bytes32 entryKey)` if the message does not exist. - - -## `contains()` -Returns whether the key exists in the inbox. - -#include_code inbox_contains l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -| Name | Type | Description | -| -------------- | ------- | ----------- | -| `_entryKey` | `bytes32` | The entry key (message hash)| -| ReturnValue | `bool` | True if contained, false otherwise| - -## `computeEntryKey()` -Computes the hash of a message. - -#include_code inbox_compute_entry_key l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -| Name | Type | Description | -| -------------- | ------- | ----------- | -| `_message` | `L1ToL2Msg` | The message to compute hash for | -| ReturnValue | `bytes32` | The hash of the message | +- Will revert with `Inbox__Unauthorized()` if `msg.sender != ROLLUP` (rollup contract is sometimes referred to as state transitioner in the docs). diff --git a/docs/docs/developers/contracts/writing_contracts/portals/communicate_with_portal.md b/docs/docs/developers/contracts/writing_contracts/portals/communicate_with_portal.md index 3946fb58ba0..8d559abe6f0 100644 --- a/docs/docs/developers/contracts/writing_contracts/portals/communicate_with_portal.md +++ b/docs/docs/developers/contracts/writing_contracts/portals/communicate_with_portal.md @@ -10,23 +10,22 @@ Follow the [token bridge tutorial](../../../tutorials/token_portal/main.md) for Whether it is tokens or other information being passed to the rollup, the portal should use the `Inbox` to do it. -The `Inbox` can be seen as a mailbox to the rollup, portals put messages into the box, and the sequencers then decide which of these message they want to include in their blocks (each message has a fee attached to it, so there is a fee market here). +The `Inbox` can be seen as a mailbox to the rollup, portals put messages into the box, and the sequencer then consumes a batch of messages from the box and include it in their blocks. When sending messages, we need to specify quite a bit of information beyond just the content that we are sharing. Namely we need to specify: | Name | Type | Description | | ----------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Recipient | `L2Actor` | The message recipient. This **MUST** match the rollup version and an Aztec contract that is **attached** to the contract making this call. If the recipient is not attached to the caller, the message cannot be consumed by it. | -| Deadline | `uint256` | The deadline for the message to be consumed. If the message has not been removed from the `Inbox` and included in a rollup block by this point, it can be _canceled_ by the portal (the portal must implement logic to cancel). | -| Content | `field` (~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to be a single field. If the content is small enough it can just be passed along, otherwise it should be hashed and the hash passed along (you can use our [`Hash`](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Hash.sol) utilities with `sha256ToField` functions) | | Secret Hash | `field` (~254 bits) | A hash of a secret that is used when consuming the message on L2. Keep this preimage a secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed) - so make sure your app keeps track of the pre-images! Use the [`computeMessageSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/secrets.ts) to compute it from a secret. | -| Fee | `uint64` | The fee to the sequencer for including the message. This is the amount of ETH that the sequencer will receive for including the message. Note that it is not a full `uint256` but only `uint64` | +| Content | `field` (~254 bits) | The content of the message. This is the data that will be passed to the recipient. The content is limited to be a single field. If the content is small enough it can just be passed along, otherwise it should be hashed and the hash passed along (you can use our [`Hash`](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/src/core/libraries/Hash.sol) utilities with `sha256ToField` functions) With all that information at hand, we can call the `sendL2Message` function on the Inbox. The function will return a `field` (inside `bytes32`) that is the hash of the message. This hash can be used as an identifier to spot when your message has been included in a rollup block. #include_code send_l1_to_l2_message l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity -As time passes, a sequencer will see your tx, the juicy fee provided and include it in a rollup block. Upon inclusion, it is removed from L1, and made available to be consumed on L2. +As time passes, a sequencer will consume the message batch your message was included in and include it in a their block. +Upon inclusion, it is made available to be consumed on L2. To consume the message, we can use the `consume_l1_to_l2_message` function within the `context` struct. @@ -77,7 +76,7 @@ To send a message to L1 from your Aztec contract, you must use the `message_port #include_code context_message_portal /noir-projects/aztec-nr/aztec/src/context/private_context.nr rust -When sending a message from L2 to L1 we don't need to pass recipient, deadline, secret nor fees. Recipient is populated with the attached portal and the remaining values are not needed as the message is inserted into the outbox at the same time as it was included in a block (for the inbox it could be inserted and then only included in rollup block later). +When sending a message from L2 to L1 we don't need to pass in a secret. :::danger Access control on the L1 portal contract is essential to prevent consumption of messages sent from the wrong L2 contract. @@ -136,23 +135,6 @@ Generally it is good practice to keep cross-chain calls simple to avoid too many Error handling for cross chain messages is handled by the application contract and not the protocol. The protocol only delivers the messages, it does not ensure that they are executed successfully. ::: -### Cancellations - -A special type of error is an underpriced transaction - it means that a message is inserted on L1, but the attached fee is too low to be included in a rollup block. - -For the case of token bridges, this could lead to funds being locked in the bridge forever, as funds are locked but the message never arrives on L2 to mint the tokens. To address this, the `Inbox` supports canceling messages after a deadline. However, this must be called by the portal itself, as it will need to "undo" the state changes is made (for example by sending the tokens back to the user). - -As this requires logic on the portal itself, it is not something that the protocol can enforce. It must be supported by the application builder when building the portal. - -The portal can call the `cancelL2Message` at the `Inbox` when `block.timestamp > deadline` for the message. - -#include_code pending_l2_cancel l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity - -Building on our token example from earlier, this can be called like: - -#include_code token_portal_cancel l1-contracts/test/portals/TokenPortal.sol solidity - -The example above ensure that the user can cancel their message if it is underpriced. ### Designated caller diff --git a/docs/docs/developers/tutorials/token_portal/cancelling_deposits.md b/docs/docs/developers/tutorials/token_portal/cancelling_deposits.md deleted file mode 100644 index a1f2e490c5a..00000000000 --- a/docs/docs/developers/tutorials/token_portal/cancelling_deposits.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Cancelling Deposits ---- - -A special type of error is an _underpriced transaction_ - it means that a message is inserted on L1, but the attached fee is too low to be included in a rollup block. In such a case your funds could be stuck in the portal and not minted on L2 (lost forever!) - -To address this, the Inbox supports cancelling messages after a deadline. However, this must be called by the portal itself, as it will need to "undo" the state changes is made (for example by sending the tokens back to the user). - -In your `TokenPortal.sol` smart contract, paste this: - -#include_code token_portal_cancel /l1-contracts/test/portals/TokenPortal.sol solidity - -To cancel a message, the portal must reconstruct it - this way we avoid storing messages in the portal itself. Note that just as with deposits we need to support cancelling messages for minting privately and publicly. - -Note that the portal uses `msg.sender` as the canceller when computing the secret hash. This is an access control mechanism to restrict only the intended address to cancel a message. - -Once the message is cancelled on the inbox, we return the funds back to the user. - -The inbox requires each message to provide a deadline by which a message must be consumed. After this time, if the message is still not consumed, the message can be cancelled. - -In the next step we will write L1 and L2 logic to withdraw funds from L2 to L1. diff --git a/docs/docs/developers/tutorials/token_portal/depositing_to_aztec.md b/docs/docs/developers/tutorials/token_portal/depositing_to_aztec.md index 9452657be1f..167d77e46b5 100644 --- a/docs/docs/developers/tutorials/token_portal/depositing_to_aztec.md +++ b/docs/docs/developers/tutorials/token_portal/depositing_to_aztec.md @@ -49,8 +49,7 @@ Here is an explanation of what it is doing: 2. We construct the “content” of the message we need to send to the recipient on Aztec. - The content is limited to a single field (~254 bits). So if the content is larger, we have to hash it and the hash can be passed along. - We use our utility method that creates a sha256 hash but truncates it to fit into a field - - Since we want to mint tokens on Aztec publicly, the content here is the amount to mint and the address on Aztec who will receive the tokens. We also include the L1 address that can cancel the L1->L2 message. Adding this into the content hash makes it so that only the appropriate person can cancel the message and not just any malicious 3rd party. - - More on cancellers can be found in [this upcoming section](./cancelling_deposits.md) + - Since we want to mint tokens on Aztec publicly, the content here is the amount to mint and the address on Aztec who will receive the tokens. - We encode this message as a mint_public function call, to specify the exact intentions and parameters we want to execute on L2. - In reality the content can be constructed in any manner as long as the sister contract on L2 can also create it. But for clarity, we are constructing the content like a abi encoded function call. - It is good practice to include all parameters used by L2 into this content (like the amount and to) so that a malicious actor can’t change the to to themselves when consuming the message. @@ -59,9 +58,7 @@ Here is an explanation of what it is doing: - recipient (called `actor` here), a struct: - the sister contract address on L2 that can consume the message. - The version - akin to THE chainID of Ethereum. By including a version, an ID, we can prevent replay attacks of the message (without this the same message might be replayable on other aztec networks that might exist). - - Deadline by which the sequencer on L2 must consume the method. After this time, the message can be canceled by the “canceller”. We will implement this functionality later in the doc. - A secret hash (fit to a field element). This is mainly used in the private domain and the preimage of the hash doesn’t need to be secret for the public flow. When consuming the message, one must provide the preimage. More on this when we create the private flow for depositing tokens. - - We also pass a fee to the sequencer for including the message. It is a uint64. 5. It returns a `bytes32 key` which is the id for this message in the Inbox. So in summary, it deposits tokens to the portal, encodes a mint message, hashes it, and sends it to the Aztec rollup via the Inbox. The L2 token contract can then mint the tokens when it processes the message. @@ -77,9 +74,9 @@ Here we want to send a message to mint tokens privately on Aztec! Some key diffe - The content hash uses a different function name - `mint_private`. This is done to make it easy to separate concerns. If the contentHash between the public and private message was the same, then an attacker could consume a private message publicly! - Since we want to mint tokens privately, we shouldn’t specify a `to` Aztec address (remember that Ethereum is completely public). Instead, we will use a secret hash - `secretHashForRedeemingMintedNotes`. Only he who knows the preimage to the secret hash can actually mint the notes. This is similar to the mechanism we use for message consumption on L2 - Like with the public flow, we move the user’s funds to the portal -- We now send the message to the inbox with the `fee`, `deadline`, the `recipient` (the sister contract on L2 along with the version of aztec the message is intended for) and the `secretHashForL2MessageConsumption` (such that on L2, the consumption of the message can be private). +- We now send the message to the inbox with the `recipient` (the sister contract on L2 along with the version of aztec the message is intended for) and the `secretHashForL2MessageConsumption` (such that on L2, the consumption of the message can be private). -Note that because L1 is public, everyone can inspect and figure out the fee, contentHash, deadline, recipient contract address. +Note that because L1 is public, everyone can inspect and figure out the contentHash and the recipient contract address. **So how do we privately consume the message on Aztec?** diff --git a/docs/docs/developers/tutorials/token_portal/typescript_glue_code.md b/docs/docs/developers/tutorials/token_portal/typescript_glue_code.md index 6df8685c419..a81c58e1a59 100644 --- a/docs/docs/developers/tutorials/token_portal/typescript_glue_code.md +++ b/docs/docs/developers/tutorials/token_portal/typescript_glue_code.md @@ -10,15 +10,10 @@ We need some helper files that can keep our code clean. Inside your `src/test` d ```bash cd fixtures -touch utils.ts cd .. && mkdir shared && cd shared touch cross_chain_test_harness.ts ``` -In `utils.ts`, we need a delay function. Put this: - -#include_code delay yarn-project/end-to-end/src/fixtures/utils.ts typescript - In `cross_chain_test_harness.ts`, add: #include_code cross_chain_test_harness /yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts typescript @@ -47,7 +42,6 @@ import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge'; import { CrossChainTestHarness } from './shared/cross_chain_test_harness.js'; -import { delay } from './fixtures/utils.js'; import { mnemonicToAccount } from 'viem/accounts'; import { createPublicClient, createWalletClient, http } from 'viem'; import { foundry } from 'viem/chains'; diff --git a/docs/docs/developers/tutorials/uniswap/swap_publicly.md b/docs/docs/developers/tutorials/uniswap/swap_publicly.md index c6516dd3b21..ef2916a9ed0 100644 --- a/docs/docs/developers/tutorials/uniswap/swap_publicly.md +++ b/docs/docs/developers/tutorials/uniswap/swap_publicly.md @@ -33,8 +33,6 @@ The Uniswap portal must first withdraw the input tokens, then check that the swa - The address on L2 which must receive the output tokens (remember this is public flow) - The secret hash for consume the L1 to L2 message. Since this is the public flow the preimage doesn’t need to be a secret -- The deadline to consume the l1 to l2 message (this is so funds aren’t stuck in the processing state forever and the message can be cancelled. Else the swapped tokens would be stuck forever) -- The address that can cancel the message (and receive the swapped tokens) 6. We include these params in the L2 → L1 `swap_public message content` too. Under the hood, the protocol adds the sender (the Uniswap l2 contract) and the recipient (the Uniswap portal contract on L1). diff --git a/docs/docs/learn/concepts/communication/cross_chain_calls.md b/docs/docs/learn/concepts/communication/cross_chain_calls.md index bff7ed5b936..b1752f75781 100644 --- a/docs/docs/learn/concepts/communication/cross_chain_calls.md +++ b/docs/docs/learn/concepts/communication/cross_chain_calls.md @@ -128,8 +128,6 @@ struct L1ToL2Msg { L2Actor: recipient, bytes32: content, bytes32: secretHash, - uint32 deadline, - uint64 fee, } struct L2ToL1Msg { @@ -146,7 +144,7 @@ The `bytes32` elements for `content` and `secretHash` hold values that must fit :::info The nullifier computation should include the index of the message in the message tree to ensure that it is possible to send duplicate messages (e.g., 2 x deposit of 500 dai to the same account). -To make it possible to hide when a specific message is consumed, the `L1ToL2Msg` is extended with a `secretHash` field, where the `secretPreimage` is used as part of the nullifier computation. This way, it is not possible for someone just seeing the `L1ToL2Msg` on L1 to know when it is consumed on L2. Also, we include the `deadline` and `fee` values to have a fee-market for message inclusion and to ensure that messages are not stuck in the pending set forever. +To make it possible to hide when a specific message is consumed, the `L1ToL2Msg` is extended with a `secretHash` field, where the `secretPreimage` is used as part of the nullifier computation. This way, it is not possible for someone just seeing the `L1ToL2Msg` on L1 to know when it is consumed on L2. ::: ## Combined Architecture diff --git a/docs/sidebars.js b/docs/sidebars.js index 3e23ca68acd..f9b18d8372e 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -262,7 +262,6 @@ const sidebars = { "developers/tutorials/token_portal/setup", "developers/tutorials/token_portal/depositing_to_aztec", "developers/tutorials/token_portal/minting_on_aztec", - "developers/tutorials/token_portal/cancelling_deposits", "developers/tutorials/token_portal/withdrawing_to_l1", "developers/tutorials/token_portal/typescript_glue_code", ], diff --git a/l1-contracts/slither_output.md b/l1-contracts/slither_output.md index bed973ca847..2113b6c1a04 100644 --- a/l1-contracts/slither_output.md +++ b/l1-contracts/slither_output.md @@ -2,15 +2,14 @@ Summary - [pess-unprotected-setter](#pess-unprotected-setter) (1 results) (High) - [uninitialized-local](#uninitialized-local) (2 results) (Medium) - [unused-return](#unused-return) (1 results) (Medium) - - [pess-dubious-typecast](#pess-dubious-typecast) (8 results) (Medium) + - [pess-dubious-typecast](#pess-dubious-typecast) (6 results) (Medium) - [missing-zero-check](#missing-zero-check) (2 results) (Low) - [reentrancy-events](#reentrancy-events) (2 results) (Low) - - [timestamp](#timestamp) (4 results) (Low) - - [pess-public-vs-external](#pess-public-vs-external) (7 results) (Low) + - [timestamp](#timestamp) (1 results) (Low) + - [pess-public-vs-external](#pess-public-vs-external) (6 results) (Low) - [assembly](#assembly) (2 results) (Informational) - - [dead-code](#dead-code) (5 results) (Informational) + - [dead-code](#dead-code) (3 results) (Informational) - [solc-version](#solc-version) (1 results) (Informational) - - [low-level-calls](#low-level-calls) (1 results) (Informational) - [similar-names](#similar-names) (3 results) (Informational) - [constable-states](#constable-states) (1 results) (Optimization) - [pess-multiple-storage-read](#pess-multiple-storage-read) (6 results) (Optimization) @@ -18,9 +17,9 @@ Summary Impact: High Confidence: Medium - [ ] ID-0 -Function [Rollup.process(bytes,bytes32,bytes,bytes)](src/core/Rollup.sol#L58-L101) is a non-protected setter archive is written +Function [Rollup.process(bytes,bytes32,bytes,bytes)](src/core/Rollup.sol#L57-L96) is a non-protected setter archive is written -src/core/Rollup.sol#L58-L101 +src/core/Rollup.sol#L57-L96 ## uninitialized-local @@ -42,9 +41,9 @@ src/core/libraries/decoders/TxsDecoder.sol#L79 Impact: Medium Confidence: Medium - [ ] ID-3 -[Rollup.process(bytes,bytes32,bytes,bytes)](src/core/Rollup.sol#L58-L101) ignores return value by [(l1ToL2Msgs,l2ToL1Msgs) = MessagesDecoder.decode(_body)](src/core/Rollup.sol#L74) +[Rollup.process(bytes,bytes32,bytes,bytes)](src/core/Rollup.sol#L57-L96) ignores return value by [(l2ToL1Msgs) = MessagesDecoder.decode(_body)](src/core/Rollup.sol#L73) -src/core/Rollup.sol#L58-L101 +src/core/Rollup.sol#L57-L96 ## pess-dubious-typecast @@ -65,35 +64,20 @@ src/core/messagebridge/Outbox.sol#L38-L46 - [ ] ID-6 -Dubious typecast in [Inbox.sendL2Message(DataStructures.L2Actor,uint32,bytes32,bytes32)](src/core/messagebridge/Inbox.sol#L45-L91): - uint256 => uint64 casting occurs in [fee = uint64(msg.value)](src/core/messagebridge/Inbox.sol#L64) - uint256 => uint32 casting occurs in [entries.insert(key,fee,uint32(_recipient.version),_deadline,_errIncompatibleEntryArguments)](src/core/messagebridge/Inbox.sol#L76) - -src/core/messagebridge/Inbox.sol#L45-L91 - - - - [ ] ID-7 Dubious typecast in [TxsDecoder.read4(bytes,uint256)](src/core/libraries/decoders/TxsDecoder.sol#L324-L326): bytes => bytes4 casting occurs in [uint256(uint32(bytes4(slice(_data,_offset,4))))](src/core/libraries/decoders/TxsDecoder.sol#L325) src/core/libraries/decoders/TxsDecoder.sol#L324-L326 - - [ ] ID-8 + - [ ] ID-7 Dubious typecast in [MessagesDecoder.read4(bytes,uint256)](src/core/libraries/decoders/MessagesDecoder.sol#L160-L162): bytes => bytes4 casting occurs in [uint256(uint32(bytes4(_data)))](src/core/libraries/decoders/MessagesDecoder.sol#L161) src/core/libraries/decoders/MessagesDecoder.sol#L160-L162 - - [ ] ID-9 -Dubious typecast in [Inbox.batchConsume(bytes32[],address)](src/core/messagebridge/Inbox.sol#L122-L143): - uint256 => uint32 casting occurs in [expectedVersion = uint32(REGISTRY.getVersionFor(msg.sender))](src/core/messagebridge/Inbox.sol#L128) - -src/core/messagebridge/Inbox.sol#L122-L143 - - - - [ ] ID-10 + - [ ] ID-8 Dubious typecast in [HeaderLib.decode(bytes)](src/core/libraries/HeaderLib.sol#L143-L184): bytes => bytes32 casting occurs in [header.lastArchive = AppendOnlyTreeSnapshot(bytes32(_header),uint32(bytes4(_header)))](src/core/libraries/HeaderLib.sol#L151-L153) bytes => bytes4 casting occurs in [header.lastArchive = AppendOnlyTreeSnapshot(bytes32(_header),uint32(bytes4(_header)))](src/core/libraries/HeaderLib.sol#L151-L153) @@ -119,7 +103,7 @@ Dubious typecast in [HeaderLib.decode(bytes)](src/core/libraries/HeaderLib.sol#L src/core/libraries/HeaderLib.sol#L143-L184 - - [ ] ID-11 + - [ ] ID-9 Dubious typecast in [MessagesDecoder.read1(bytes,uint256)](src/core/libraries/decoders/MessagesDecoder.sol#L150-L152): bytes => bytes1 casting occurs in [uint256(uint8(bytes1(_data)))](src/core/libraries/decoders/MessagesDecoder.sol#L151) @@ -129,14 +113,14 @@ src/core/libraries/decoders/MessagesDecoder.sol#L150-L152 ## missing-zero-check Impact: Low Confidence: Medium - - [ ] ID-12 -[NewInbox.constructor(address,uint256)._rollup](src/core/messagebridge/NewInbox.sol#L41) lacks a zero-check on : - - [ROLLUP = _rollup](src/core/messagebridge/NewInbox.sol#L42) + - [ ] ID-10 +[Inbox.constructor(address,uint256)._rollup](src/core/messagebridge/Inbox.sol#L40) lacks a zero-check on : + - [ROLLUP = _rollup](src/core/messagebridge/Inbox.sol#L41) -src/core/messagebridge/NewInbox.sol#L41 +src/core/messagebridge/Inbox.sol#L40 - - [ ] ID-13 + - [ ] ID-11 [NewOutbox.constructor(address)._rollup](src/core/messagebridge/NewOutbox.sol#L31) lacks a zero-check on : - [ROLLUP_CONTRACT = _rollup](src/core/messagebridge/NewOutbox.sol#L32) @@ -146,40 +130,31 @@ src/core/messagebridge/NewOutbox.sol#L31 ## reentrancy-events Impact: Low Confidence: Medium - - [ ] ID-14 -Reentrancy in [NewInbox.sendL2Message(DataStructures.L2Actor,bytes32,bytes32)](src/core/messagebridge/NewInbox.sol#L62-L99): + - [ ] ID-12 +Reentrancy in [Inbox.sendL2Message(DataStructures.L2Actor,bytes32,bytes32)](src/core/messagebridge/Inbox.sol#L61-L95): External calls: - - [index = currentTree.insertLeaf(leaf)](src/core/messagebridge/NewInbox.sol#L95) + - [index = currentTree.insertLeaf(leaf)](src/core/messagebridge/Inbox.sol#L91) Event emitted after the call(s): - - [LeafInserted(inProgress,index,leaf)](src/core/messagebridge/NewInbox.sol#L96) + - [LeafInserted(inProgress,index,leaf)](src/core/messagebridge/Inbox.sol#L92) -src/core/messagebridge/NewInbox.sol#L62-L99 +src/core/messagebridge/Inbox.sol#L61-L95 - - [ ] ID-15 -Reentrancy in [Rollup.process(bytes,bytes32,bytes,bytes)](src/core/Rollup.sol#L58-L101): + - [ ] ID-13 +Reentrancy in [Rollup.process(bytes,bytes32,bytes,bytes)](src/core/Rollup.sol#L57-L96): External calls: - - [inbox.batchConsume(l1ToL2Msgs,msg.sender)](src/core/Rollup.sol#L90) - - [inHash = NEW_INBOX.consume()](src/core/Rollup.sol#L92) - - [outbox.sendL1Messages(l2ToL1Msgs)](src/core/Rollup.sol#L98) + - [inHash = INBOX.consume()](src/core/Rollup.sol#L87) + - [outbox.sendL1Messages(l2ToL1Msgs)](src/core/Rollup.sol#L93) Event emitted after the call(s): - - [L2BlockProcessed(header.globalVariables.blockNumber)](src/core/Rollup.sol#L100) + - [L2BlockProcessed(header.globalVariables.blockNumber)](src/core/Rollup.sol#L95) -src/core/Rollup.sol#L58-L101 +src/core/Rollup.sol#L57-L96 ## timestamp Impact: Low Confidence: Medium - - [ ] ID-16 -[Inbox.batchConsume(bytes32[],address)](src/core/messagebridge/Inbox.sol#L122-L143) uses timestamp for comparisons - Dangerous comparisons: - - [block.timestamp > entry.deadline](src/core/messagebridge/Inbox.sol#L136) - -src/core/messagebridge/Inbox.sol#L122-L143 - - - - [ ] ID-17 + - [ ] ID-14 [HeaderLib.validate(HeaderLib.Header,uint256,uint256,bytes32)](src/core/libraries/HeaderLib.sol#L106-L136) uses timestamp for comparisons Dangerous comparisons: - [_header.globalVariables.timestamp > block.timestamp](src/core/libraries/HeaderLib.sol#L120) @@ -187,47 +162,38 @@ src/core/messagebridge/Inbox.sol#L122-L143 src/core/libraries/HeaderLib.sol#L106-L136 - - [ ] ID-18 -[Inbox.sendL2Message(DataStructures.L2Actor,uint32,bytes32,bytes32)](src/core/messagebridge/Inbox.sol#L45-L91) uses timestamp for comparisons - Dangerous comparisons: - - [_deadline <= block.timestamp](src/core/messagebridge/Inbox.sol#L54) - -src/core/messagebridge/Inbox.sol#L45-L91 - - - - [ ] ID-19 -[Inbox.cancelL2Message(DataStructures.L1ToL2Msg,address)](src/core/messagebridge/Inbox.sol#L102-L113) uses timestamp for comparisons - Dangerous comparisons: - - [block.timestamp <= _message.deadline](src/core/messagebridge/Inbox.sol#L108) - -src/core/messagebridge/Inbox.sol#L102-L113 - - ## pess-public-vs-external Impact: Low Confidence: Medium - - [ ] ID-20 + - [ ] ID-15 The following public functions could be turned into external in [FrontierMerkle](src/core/messagebridge/frontier_tree/Frontier.sol#L7-L93) contract: [FrontierMerkle.constructor(uint256)](src/core/messagebridge/frontier_tree/Frontier.sol#L19-L27) src/core/messagebridge/frontier_tree/Frontier.sol#L7-L93 - - [ ] ID-21 + - [ ] ID-16 The following public functions could be turned into external in [Registry](src/core/messagebridge/Registry.sol#L22-L129) contract: [Registry.constructor()](src/core/messagebridge/Registry.sol#L29-L33) src/core/messagebridge/Registry.sol#L22-L129 - - [ ] ID-22 -The following public functions could be turned into external in [Rollup](src/core/Rollup.sol#L30-L110) contract: - [Rollup.constructor(IRegistry,IAvailabilityOracle)](src/core/Rollup.sol#L43-L49) + - [ ] ID-17 +The following public functions could be turned into external in [Inbox](src/core/messagebridge/Inbox.sol#L24-L124) contract: + [Inbox.constructor(address,uint256)](src/core/messagebridge/Inbox.sol#L40-L51) -src/core/Rollup.sol#L30-L110 +src/core/messagebridge/Inbox.sol#L24-L124 - - [ ] ID-23 + - [ ] ID-18 +The following public functions could be turned into external in [Rollup](src/core/Rollup.sol#L29-L105) contract: + [Rollup.constructor(IRegistry,IAvailabilityOracle)](src/core/Rollup.sol#L42-L48) + +src/core/Rollup.sol#L29-L105 + + + - [ ] ID-19 The following public functions could be turned into external in [Outbox](src/core/messagebridge/Outbox.sol#L21-L148) contract: [Outbox.constructor(address)](src/core/messagebridge/Outbox.sol#L29-L31) [Outbox.get(bytes32)](src/core/messagebridge/Outbox.sol#L77-L84) @@ -236,32 +202,17 @@ The following public functions could be turned into external in [Outbox](src/cor src/core/messagebridge/Outbox.sol#L21-L148 - - [ ] ID-24 -The following public functions could be turned into external in [Inbox](src/core/messagebridge/Inbox.sol#L21-L231) contract: - [Inbox.constructor(address)](src/core/messagebridge/Inbox.sol#L30-L32) - [Inbox.contains(bytes32)](src/core/messagebridge/Inbox.sol#L174-L176) - -src/core/messagebridge/Inbox.sol#L21-L231 - - - - [ ] ID-25 + - [ ] ID-20 The following public functions could be turned into external in [NewOutbox](src/core/messagebridge/NewOutbox.sol#L18-L132) contract: [NewOutbox.constructor(address)](src/core/messagebridge/NewOutbox.sol#L31-L33) src/core/messagebridge/NewOutbox.sol#L18-L132 - - [ ] ID-26 -The following public functions could be turned into external in [NewInbox](src/core/messagebridge/NewInbox.sol#L25-L128) contract: - [NewInbox.constructor(address,uint256)](src/core/messagebridge/NewInbox.sol#L41-L52) - -src/core/messagebridge/NewInbox.sol#L25-L128 - - ## assembly Impact: Informational Confidence: High - - [ ] ID-27 + - [ ] ID-21 [MessagesDecoder.decode(bytes)](src/core/libraries/decoders/MessagesDecoder.sol#L60-L142) uses assembly - [INLINE ASM](src/core/libraries/decoders/MessagesDecoder.sol#L79-L81) - [INLINE ASM](src/core/libraries/decoders/MessagesDecoder.sol#L112-L118) @@ -269,7 +220,7 @@ Confidence: High src/core/libraries/decoders/MessagesDecoder.sol#L60-L142 - - [ ] ID-28 + - [ ] ID-22 [TxsDecoder.computeRoot(bytes32[])](src/core/libraries/decoders/TxsDecoder.sol#L256-L275) uses assembly - [INLINE ASM](src/core/libraries/decoders/TxsDecoder.sol#L263-L265) @@ -279,31 +230,19 @@ src/core/libraries/decoders/TxsDecoder.sol#L256-L275 ## dead-code Impact: Informational Confidence: Medium - - [ ] ID-29 -[Inbox._errIncompatibleEntryArguments(bytes32,uint64,uint64,uint32,uint32,uint32,uint32)](src/core/messagebridge/Inbox.sol#L212-L230) is never used and should be removed - -src/core/messagebridge/Inbox.sol#L212-L230 - - - - [ ] ID-30 + - [ ] ID-23 [Outbox._errNothingToConsume(bytes32)](src/core/messagebridge/Outbox.sol#L114-L116) is never used and should be removed src/core/messagebridge/Outbox.sol#L114-L116 - - [ ] ID-31 -[Hash.sha256ToField(bytes32)](src/core/libraries/Hash.sol#L59-L61) is never used and should be removed - -src/core/libraries/Hash.sol#L59-L61 - - - - [ ] ID-32 -[Inbox._errNothingToConsume(bytes32)](src/core/messagebridge/Inbox.sol#L197-L199) is never used and should be removed + - [ ] ID-24 +[Hash.sha256ToField(bytes32)](src/core/libraries/Hash.sol#L52-L54) is never used and should be removed -src/core/messagebridge/Inbox.sol#L197-L199 +src/core/libraries/Hash.sol#L52-L54 - - [ ] ID-33 + - [ ] ID-25 [Outbox._errIncompatibleEntryArguments(bytes32,uint64,uint64,uint32,uint32,uint32,uint32)](src/core/messagebridge/Outbox.sol#L129-L147) is never used and should be removed src/core/messagebridge/Outbox.sol#L129-L147 @@ -312,83 +251,73 @@ src/core/messagebridge/Outbox.sol#L129-L147 ## solc-version Impact: Informational Confidence: High - - [ ] ID-34 + - [ ] ID-26 solc-0.8.23 is not recommended for deployment -## low-level-calls -Impact: Informational -Confidence: High - - [ ] ID-35 -Low level call in [Inbox.withdrawFees()](src/core/messagebridge/Inbox.sol#L148-L153): - - [(success) = msg.sender.call{value: balance}()](src/core/messagebridge/Inbox.sol#L151) - -src/core/messagebridge/Inbox.sol#L148-L153 - - ## similar-names Impact: Informational Confidence: Medium - - [ ] ID-36 + - [ ] ID-27 Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L130) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L123) src/core/libraries/ConstantsGen.sol#L130 - - [ ] ID-37 + - [ ] ID-28 Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L110) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L111) src/core/libraries/ConstantsGen.sol#L110 - - [ ] ID-38 -Variable [Rollup.AVAILABILITY_ORACLE](src/core/Rollup.sol#L33) is too similar to [Rollup.constructor(IRegistry,IAvailabilityOracle)._availabilityOracle](src/core/Rollup.sol#L43) + - [ ] ID-29 +Variable [Rollup.AVAILABILITY_ORACLE](src/core/Rollup.sol#L32) is too similar to [Rollup.constructor(IRegistry,IAvailabilityOracle)._availabilityOracle](src/core/Rollup.sol#L42) -src/core/Rollup.sol#L33 +src/core/Rollup.sol#L32 ## constable-states Impact: Optimization Confidence: High - - [ ] ID-39 -[Rollup.lastWarpedBlockTs](src/core/Rollup.sol#L41) should be constant + - [ ] ID-30 +[Rollup.lastWarpedBlockTs](src/core/Rollup.sol#L40) should be constant -src/core/Rollup.sol#L41 +src/core/Rollup.sol#L40 ## pess-multiple-storage-read Impact: Optimization Confidence: High - - [ ] ID-40 + - [ ] ID-31 In a function [NewOutbox.insert(uint256,bytes32,uint256)](src/core/messagebridge/NewOutbox.sol#L44-L64) variable [NewOutbox.roots](src/core/messagebridge/NewOutbox.sol#L29) is read multiple times src/core/messagebridge/NewOutbox.sol#L44-L64 - - [ ] ID-41 -In a function [NewInbox.sendL2Message(DataStructures.L2Actor,bytes32,bytes32)](src/core/messagebridge/NewInbox.sol#L62-L99) variable [NewInbox.inProgress](src/core/messagebridge/NewInbox.sol#L37) is read multiple times + - [ ] ID-32 +In a function [Inbox.consume()](src/core/messagebridge/Inbox.sol#L104-L123) variable [Inbox.toConsume](src/core/messagebridge/Inbox.sol#L34) is read multiple times -src/core/messagebridge/NewInbox.sol#L62-L99 +src/core/messagebridge/Inbox.sol#L104-L123 - - [ ] ID-42 -In a function [FrontierMerkle.root()](src/core/messagebridge/frontier_tree/Frontier.sol#L43-L76) variable [FrontierMerkle.HEIGHT](src/core/messagebridge/frontier_tree/Frontier.sol#L8) is read multiple times + - [ ] ID-33 +In a function [Inbox.consume()](src/core/messagebridge/Inbox.sol#L104-L123) variable [Inbox.inProgress](src/core/messagebridge/Inbox.sol#L36) is read multiple times -src/core/messagebridge/frontier_tree/Frontier.sol#L43-L76 +src/core/messagebridge/Inbox.sol#L104-L123 - - [ ] ID-43 -In a function [NewInbox.consume()](src/core/messagebridge/NewInbox.sol#L108-L127) variable [NewInbox.inProgress](src/core/messagebridge/NewInbox.sol#L37) is read multiple times + - [ ] ID-34 +In a function [FrontierMerkle.root()](src/core/messagebridge/frontier_tree/Frontier.sol#L43-L76) variable [FrontierMerkle.HEIGHT](src/core/messagebridge/frontier_tree/Frontier.sol#L8) is read multiple times -src/core/messagebridge/NewInbox.sol#L108-L127 +src/core/messagebridge/frontier_tree/Frontier.sol#L43-L76 - - [ ] ID-44 -In a function [NewInbox.consume()](src/core/messagebridge/NewInbox.sol#L108-L127) variable [NewInbox.toConsume](src/core/messagebridge/NewInbox.sol#L35) is read multiple times + - [ ] ID-35 +In a function [Inbox.sendL2Message(DataStructures.L2Actor,bytes32,bytes32)](src/core/messagebridge/Inbox.sol#L61-L95) variable [Inbox.inProgress](src/core/messagebridge/Inbox.sol#L36) is read multiple times -src/core/messagebridge/NewInbox.sol#L108-L127 +src/core/messagebridge/Inbox.sol#L61-L95 - - [ ] ID-45 + - [ ] ID-36 In a function [FrontierMerkle.root()](src/core/messagebridge/frontier_tree/Frontier.sol#L43-L76) variable [FrontierMerkle.frontier](src/core/messagebridge/frontier_tree/Frontier.sol#L13) is read multiple times src/core/messagebridge/frontier_tree/Frontier.sol#L43-L76 diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index f82ca13559c..6fd21cf6792 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -6,7 +6,6 @@ pragma solidity >=0.8.18; import {IRollup} from "./interfaces/IRollup.sol"; import {IAvailabilityOracle} from "./interfaces/IAvailabilityOracle.sol"; import {IInbox} from "./interfaces/messagebridge/IInbox.sol"; -import {INewInbox} from "./interfaces/messagebridge/INewInbox.sol"; import {IOutbox} from "./interfaces/messagebridge/IOutbox.sol"; import {IRegistry} from "./interfaces/messagebridge/IRegistry.sol"; @@ -19,7 +18,7 @@ import {Constants} from "./libraries/ConstantsGen.sol"; // Contracts import {MockVerifier} from "../mock/MockVerifier.sol"; -import {NewInbox} from "./messagebridge/NewInbox.sol"; +import {Inbox} from "./messagebridge/Inbox.sol"; /** * @title Rollup @@ -31,7 +30,7 @@ contract Rollup is IRollup { MockVerifier public immutable VERIFIER; IRegistry public immutable REGISTRY; IAvailabilityOracle public immutable AVAILABILITY_ORACLE; - INewInbox public immutable NEW_INBOX; + IInbox public immutable INBOX; uint256 public immutable VERSION; bytes32 public archive; // Root of the archive tree @@ -44,7 +43,7 @@ contract Rollup is IRollup { VERIFIER = new MockVerifier(); REGISTRY = _registry; AVAILABILITY_ORACLE = _availabilityOracle; - NEW_INBOX = new NewInbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT); + INBOX = new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT); VERSION = 1; } @@ -58,7 +57,7 @@ contract Rollup is IRollup { function process( bytes calldata _header, bytes32 _archive, - bytes calldata _body, // TODO(#4492) Nuke this when updating to the new message model + bytes calldata _body, // TODO(#5073) Nuke this when updating to the new message model bytes memory _proof ) external override(IRollup) { // Decode and validate header @@ -71,7 +70,7 @@ contract Rollup is IRollup { } // Decode the cross-chain messages (Will be removed as part of message model change) - (,, bytes32[] memory l1ToL2Msgs, bytes32[] memory l2ToL1Msgs) = MessagesDecoder.decode(_body); + (,,, bytes32[] memory l2ToL1Msgs) = MessagesDecoder.decode(_body); bytes32[] memory publicInputs = new bytes32[](1); publicInputs[0] = _computePublicInputHash(_header, _archive); @@ -85,11 +84,7 @@ contract Rollup is IRollup { archive = _archive; lastBlockTs = block.timestamp; - // @todo (issue #605) handle fee collector - IInbox inbox = REGISTRY.getInbox(); - inbox.batchConsume(l1ToL2Msgs, msg.sender); - - bytes32 inHash = NEW_INBOX.consume(); + bytes32 inHash = INBOX.consume(); if (header.contentCommitment.inHash != inHash) { revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash); } diff --git a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol index 74f21425397..8bc025a54f0 100644 --- a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol +++ b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol @@ -10,95 +10,32 @@ import {DataStructures} from "../../libraries/DataStructures.sol"; * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. */ interface IInbox { - event MessageAdded( - bytes32 indexed entryKey, - address indexed sender, - bytes32 indexed recipient, - uint256 senderChainId, - uint256 recipientVersion, - uint32 deadline, - uint64 fee, - bytes32 content, - bytes32 secretHash - ); - - event L1ToL2MessageCancelled(bytes32 indexed entryKey); + event LeafInserted(uint256 indexed blockNumber, uint256 index, bytes32 value); // docs:start:send_l1_to_l2_message /** - * @notice Inserts an entry into the Inbox - * @dev Will emit `MessageAdded` with data for easy access by the sequencer - * @dev msg.value - The fee provided to sequencer for including the entry - * @param _recipient - The recipient of the entry - * @param _deadline - The deadline to consume a message. Only after it, can a message be cancelled. - * @param _content - The content of the entry (application specific) - * @param _secretHash - The secret hash of the entry (make it possible to hide when a specific entry is consumed on L2) - * @return The key of the entry in the set + * @notice Inserts a new message into the Inbox + * @dev Emits `LeafInserted` with data for easy access by the sequencer + * @param _recipient - The recipient of the message + * @param _content - The content of the message (application specific) + * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @return The key of the message in the set */ function sendL2Message( DataStructures.L2Actor memory _recipient, - uint32 _deadline, bytes32 _content, bytes32 _secretHash - ) external payable returns (bytes32); + ) external returns (bytes32); // docs:end:send_l1_to_l2_message - // docs:start:pending_l2_cancel - /** - * @notice Cancel a pending L2 message - * @dev Will revert if the deadline have not been crossed - message only cancellable past the deadline - * so it cannot be yanked away while the sequencer is building a block including it - * @dev Must be called by portal that inserted the entry - * @param _message - The content of the entry (application specific) - * @param _feeCollector - The address to receive the "fee" - * @return entryKey - The key of the entry removed - */ - function cancelL2Message(DataStructures.L1ToL2Msg memory _message, address _feeCollector) - external - returns (bytes32 entryKey); - // docs:end:pending_l2_cancel - - // docs:start:inbox_batch_consume + // docs:start:consume /** - * @notice Batch consumes entries from the Inbox + * @notice Consumes the current tree, and starts a new one if needed * @dev Only callable by the rollup contract - * @dev Will revert if the message is already past deadline - * @param _entryKeys - Array of entry keys (hash of the messages) - * @param _feeCollector - The address to receive the "fee" - */ - function batchConsume(bytes32[] memory _entryKeys, address _feeCollector) external; - // docs:end:inbox_batch_consume - - // docs:start:inbox_withdraw_fees - /** - * @notice Withdraws fees accrued by the sequencer + * @dev In the first iteration we return empty tree root because first block's messages tree is always + * empty because there has to be a 1 block lag to prevent sequencer DOS attacks + * @return The root of the consumed tree */ - function withdrawFees() external; - // docs:end:inbox_withdraw_fees - - // docs:start:inbox_get - /** - * @notice Fetch an entry - * @param _entryKey - The key to lookup - * @return The entry matching the provided key - */ - function get(bytes32 _entryKey) external view returns (DataStructures.Entry memory); - // docs:end:inbox_get - - // docs:start:inbox_contains - /** - * @notice Check if entry exists - * @param _entryKey - The key to lookup - * @return True if entry exists, false otherwise - */ - function contains(bytes32 _entryKey) external view returns (bool); - // docs:end:inbox_contains - - // docs:start:inbox_compute_entry_key - /// @notice Given a message, computes an entry key for the Inbox - function computeEntryKey(DataStructures.L1ToL2Msg memory _message) - external - pure - returns (bytes32); - // docs:end:inbox_compute_entry_key + function consume() external returns (bytes32); + // docs:end:consume } diff --git a/l1-contracts/src/core/interfaces/messagebridge/INewInbox.sol b/l1-contracts/src/core/interfaces/messagebridge/INewInbox.sol deleted file mode 100644 index 851d76a85c6..00000000000 --- a/l1-contracts/src/core/interfaces/messagebridge/INewInbox.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Aztec Labs. -pragma solidity >=0.8.18; - -import {DataStructures} from "../../libraries/DataStructures.sol"; - -/** - * @title Inbox - * @author Aztec Labs - * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. - */ -// TODO: rename to IInbox once all the pieces of the new message model are in place. -interface INewInbox { - event LeafInserted(uint256 indexed blockNumber, uint256 index, bytes32 value); - - /** - * @notice Inserts a new message into the Inbox - * @dev Emits `LeafInserted` with data for easy access by the sequencer - * @param _recipient - The recipient of the message - * @param _content - The content of the message (application specific) - * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) - * @return The key of the message in the set - */ - function sendL2Message( - DataStructures.L2Actor memory _recipient, - bytes32 _content, - bytes32 _secretHash - ) external returns (bytes32); - - /** - * @notice Consumes the current tree, and starts a new one if needed - * @dev Only callable by the rollup contract - * @dev In the first iteration we return empty tree root because first block's messages tree is always - * empty because there has to be a 1 block lag to prevent sequencer DOS attacks - * @return The root of the consumed tree - */ - function consume() external returns (bytes32); -} diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index fc5280df86c..444c3de0ded 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -107,7 +107,7 @@ library Constants { uint256 internal constant FUNCTION_LEAF_PREIMAGE_LENGTH = 5; uint256 internal constant GLOBAL_VARIABLES_LENGTH = 6; uint256 internal constant HEADER_LENGTH = 23; - uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 8; + uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 6; uint256 internal constant L2_TO_L1_MESSAGE_LENGTH = 2; uint256 internal constant NULLIFIER_KEY_VALIDATION_REQUEST_LENGTH = 4; uint256 internal constant NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5; diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index fd53fae1365..8a8e8d3a0e5 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -55,16 +55,12 @@ library DataStructures { * @param recipient - The recipient of the message * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2). - * @param deadline - The deadline to consume a message. Only after it, can a message be cancelled. - * @param fee - The fee provided to sequencer for including the entry */ struct L1ToL2Msg { L1Actor sender; L2Actor recipient; bytes32 content; bytes32 secretHash; - uint32 deadline; - uint64 fee; } // docs:end:l1_to_l2_msg diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 422bbfafff6..07c288df57d 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -11,23 +11,7 @@ pragma solidity >=0.8.18; */ library Errors { // Inbox - error Inbox__DeadlineBeforeNow(); // 0xbf94a5dc - error Inbox__NotPastDeadline(); //0x3218ad9e - error Inbox__PastDeadline(); // 0x1eb114ea - error Inbox__InvalidVersion(uint256 entry, uint256 rollup); // 0x60be5dca - error Inbox__FeeTooHigh(); // 0x6f478f42 - error Inbox__FailedToWithdrawFees(); // 0xbc66d464 error Inbox__Unauthorized(); // 0xe5336a6b - error Inbox__NothingToConsume(bytes32 entryKey); // 0xdd7e995e - error Inbox__IncompatibleEntryArguments( - bytes32 entryKey, - uint64 storedFee, - uint64 feePassed, - uint32 storedVersion, - uint32 versionPassed, - uint32 storedDeadline, - uint32 deadlinePassed - ); // 0xd483d8f2 error Inbox__ActorTooLarge(bytes32 actor); // 0xa776a06e error Inbox__ContentTooLarge(bytes32 content); // 0x47452014 error Inbox__SecretHashTooLarge(bytes32 secretHash); // 0xecde7e2c diff --git a/l1-contracts/src/core/libraries/Hash.sol b/l1-contracts/src/core/libraries/Hash.sol index 975bb0f29a4..d859690b1a9 100644 --- a/l1-contracts/src/core/libraries/Hash.sol +++ b/l1-contracts/src/core/libraries/Hash.sol @@ -20,14 +20,7 @@ library Hash { */ function sha256ToField(DataStructures.L1ToL2Msg memory _message) internal pure returns (bytes32) { return sha256ToField( - abi.encode( - _message.sender, - _message.recipient, - _message.content, - _message.secretHash, - _message.deadline, - _message.fee - ) + abi.encode(_message.sender, _message.recipient, _message.content, _message.secretHash) ); } diff --git a/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol b/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol index 95ad430f138..4ebb2660936 100644 --- a/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol +++ b/l1-contracts/src/core/libraries/decoders/TxsDecoder.sol @@ -81,7 +81,7 @@ library TxsDecoder { { // L1 to L2 messages - // TODO(#4492): update this when implementing the new message model + // TODO(#5073): update this uint256 count = read4(_body, offset); offset += 0x4 + count * 0x20; diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index f00d0dba100..aa1a2959fe5 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -3,15 +3,18 @@ pragma solidity >=0.8.18; // Interfaces -import {IInbox} from "../interfaces/messagebridge/IInbox.sol"; +import {IFrontier} from "../interfaces/messagebridge/IFrontier.sol"; import {IRegistry} from "../interfaces/messagebridge/IRegistry.sol"; +import {IInbox} from "../interfaces/messagebridge/IInbox.sol"; // Libraries import {Constants} from "../libraries/ConstantsGen.sol"; import {DataStructures} from "../libraries/DataStructures.sol"; import {Errors} from "../libraries/Errors.sol"; import {Hash} from "../libraries/Hash.sol"; -import {MessageBox} from "../libraries/MessageBox.sol"; + +// Contracts +import {FrontierMerkle} from "./frontier_tree/Frontier.sol"; /** * @title Inbox @@ -19,213 +22,103 @@ import {MessageBox} from "../libraries/MessageBox.sol"; * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. */ contract Inbox is IInbox { - using MessageBox for mapping(bytes32 entryKey => DataStructures.Entry entry); using Hash for DataStructures.L1ToL2Msg; - IRegistry public immutable REGISTRY; + address public immutable ROLLUP; + + uint256 internal immutable HEIGHT; + uint256 internal immutable SIZE; + bytes32 internal immutable EMPTY_ROOT; // The root of an empty frontier tree + + // Number of a tree which is ready to be consumed + uint256 public toConsume = Constants.INITIAL_L2_BLOCK_NUM; + // Number of a tree which is currently being filled + uint256 public inProgress = Constants.INITIAL_L2_BLOCK_NUM + 1; - mapping(bytes32 entryKey => DataStructures.Entry entry) internal entries; - mapping(address account => uint256 balance) public feesAccrued; + mapping(uint256 blockNumber => IFrontier tree) internal trees; - constructor(address _registry) { - REGISTRY = IRegistry(_registry); + constructor(address _rollup, uint256 _height) { + ROLLUP = _rollup; + + HEIGHT = _height; + SIZE = 2 ** _height; + + // We deploy the first tree + IFrontier firstTree = IFrontier(new FrontierMerkle(_height)); + trees[inProgress] = firstTree; + + EMPTY_ROOT = firstTree.root(); } /** - * @notice Inserts an entry into the Inbox - * @dev Will emit `MessageAdded` with data for easy access by the sequencer - * @dev msg.value - The fee provided to sequencer for including the entry - * @param _recipient - The recipient of the entry - * @param _deadline - The deadline to consume a message. Only after it, can a message be cancelled. - * it is uint32 to for slot packing of the Entry struct. Should work until Feb 2106. - * @param _content - The content of the entry (application specific) - * @param _secretHash - The secret hash of the entry (make it possible to hide when a specific entry is consumed on L2) - * @return The key of the entry in the set + * @notice Inserts a new message into the Inbox + * @dev Emits `LeafInserted` with data for easy access by the sequencer + * @param _recipient - The recipient of the message + * @param _content - The content of the message (application specific) + * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @return The key of the message in the set */ function sendL2Message( DataStructures.L2Actor memory _recipient, - uint32 _deadline, bytes32 _content, bytes32 _secretHash - ) external payable override(IInbox) returns (bytes32) { + ) external override(IInbox) returns (bytes32) { if (uint256(_recipient.actor) > Constants.MAX_FIELD_VALUE) { revert Errors.Inbox__ActorTooLarge(_recipient.actor); } - if (_deadline <= block.timestamp) revert Errors.Inbox__DeadlineBeforeNow(); if (uint256(_content) > Constants.MAX_FIELD_VALUE) { revert Errors.Inbox__ContentTooLarge(_content); } if (uint256(_secretHash) > Constants.MAX_FIELD_VALUE) { revert Errors.Inbox__SecretHashTooLarge(_secretHash); } - // `fee` is uint64 for slot packing of the Entry struct. uint64 caps at ~18.4 ETH which should be enough. - // we revert here to safely cast msg.value into uint64. - if (msg.value > type(uint64).max) revert Errors.Inbox__FeeTooHigh(); - uint64 fee = uint64(msg.value); + + IFrontier currentTree = trees[inProgress]; + if (currentTree.isFull()) { + inProgress += 1; + currentTree = IFrontier(new FrontierMerkle(HEIGHT)); + trees[inProgress] = currentTree; + } + DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ sender: DataStructures.L1Actor(msg.sender, block.chainid), recipient: _recipient, content: _content, - secretHash: _secretHash, - deadline: _deadline, - fee: fee + secretHash: _secretHash }); - bytes32 key = computeEntryKey(message); - // Unsafe cast to uint32, but as we increment by 1 for versions to lookup the snapshots, we should be fine. - entries.insert(key, fee, uint32(_recipient.version), _deadline, _errIncompatibleEntryArguments); - - emit MessageAdded( - key, - message.sender.actor, - message.recipient.actor, - message.sender.chainId, - message.recipient.version, - message.deadline, - message.fee, - message.content, - message.secretHash - ); - - return key; - } + bytes32 leaf = message.sha256ToField(); + uint256 index = currentTree.insertLeaf(leaf); + emit LeafInserted(inProgress, index, leaf); - /** - * @notice Cancel a pending L2 message - * @dev Will revert if the deadline have not been crossed - message only cancellable past the deadline - * so it cannot be yanked away while the sequencer is building a block including it - * @dev Must be called by portal that inserted the entry - * @param _message - The content of the entry (application specific) - * @param _feeCollector - The address to receive the "fee" - * @return entryKey - The key of the entry removed - */ - function cancelL2Message(DataStructures.L1ToL2Msg memory _message, address _feeCollector) - external - override(IInbox) - returns (bytes32 entryKey) - { - if (msg.sender != _message.sender.actor) revert Errors.Inbox__Unauthorized(); - if (block.timestamp <= _message.deadline) revert Errors.Inbox__NotPastDeadline(); - entryKey = computeEntryKey(_message); - entries.consume(entryKey, _errNothingToConsume); - feesAccrued[_feeCollector] += _message.fee; - emit L1ToL2MessageCancelled(entryKey); + return leaf; } /** - * @notice Batch consumes entries from the Inbox + * @notice Consumes the current tree, and starts a new one if needed * @dev Only callable by the rollup contract - * @dev Will revert if the message is already past deadline - * @param _entryKeys - Array of entry keys (hash of the messages) - * @param _feeCollector - The address to receive the "fee" + * @dev In the first iteration we return empty tree root because first block's messages tree is always + * empty because there has to be a 1 block lag to prevent sequencer DOS attacks + * @return The root of the consumed tree */ - function batchConsume(bytes32[] memory _entryKeys, address _feeCollector) - external - override(IInbox) - { - uint256 totalFee = 0; - // This MUST revert if not called by a listed rollup contract - uint32 expectedVersion = uint32(REGISTRY.getVersionFor(msg.sender)); - for (uint256 i = 0; i < _entryKeys.length; i++) { - if (_entryKeys[i] == bytes32(0)) continue; - DataStructures.Entry memory entry = get(_entryKeys[i]); - if (entry.version != expectedVersion) { - revert Errors.Inbox__InvalidVersion(entry.version, expectedVersion); - } - // cant consume if we are already past deadline. - if (block.timestamp > entry.deadline) revert Errors.Inbox__PastDeadline(); - entries.consume(_entryKeys[i], _errNothingToConsume); - totalFee += entry.fee; - } - if (totalFee > 0) { - feesAccrued[_feeCollector] += totalFee; + function consume() external override(IInbox) returns (bytes32) { + if (msg.sender != ROLLUP) { + revert Errors.Inbox__Unauthorized(); } - } - /** - * @notice Withdraws fees accrued by the sequencer - */ - function withdrawFees() external override(IInbox) { - uint256 balance = feesAccrued[msg.sender]; - feesAccrued[msg.sender] = 0; - (bool success,) = msg.sender.call{value: balance}(""); - if (!success) revert Errors.Inbox__FailedToWithdrawFees(); - } - - /** - * @notice Fetch an entry - * @param _entryKey - The key to lookup - * @return The entry matching the provided key - */ - function get(bytes32 _entryKey) - public - view - override(IInbox) - returns (DataStructures.Entry memory) - { - return entries.get(_entryKey, _errNothingToConsume); - } - - /** - * @notice Check if entry exists - * @param _entryKey - The key to lookup - * @return True if entry exists, false otherwise - */ - function contains(bytes32 _entryKey) public view override(IInbox) returns (bool) { - return entries.contains(_entryKey); - } + bytes32 root = EMPTY_ROOT; + if (toConsume > Constants.INITIAL_L2_BLOCK_NUM) { + root = trees[toConsume].root(); + } - /** - * @notice Given a message, computes an entry key for the Inbox - * @param _message - The L1 to L2 message - * @return The hash of the message (used as the key of the entry in the set) - */ - function computeEntryKey(DataStructures.L1ToL2Msg memory _message) - public - pure - override(IInbox) - returns (bytes32) - { - return _message.sha256ToField(); - } + // If we are "catching up" we skip the tree creation as it is already there + if (toConsume + 1 == inProgress) { + inProgress += 1; + trees[inProgress] = IFrontier(new FrontierMerkle(HEIGHT)); + } - /** - * @notice Error function passed in cases where there might be nothing to consume - * @dev Used to have message box library throw `Inbox__` prefixed errors - * @param _entryKey - The key to lookup - */ - function _errNothingToConsume(bytes32 _entryKey) internal pure { - revert Errors.Inbox__NothingToConsume(_entryKey); - } + toConsume += 1; - /** - * @notice Error function passed in cases where insertions can fail - * @dev Used to have message box library throw `Inbox__` prefixed errors - * @param _entryKey - The key to lookup - * @param _storedFee - The fee stored in the entry - * @param _feePassed - The fee passed into the insertion - * @param _storedVersion - The version stored in the entry - * @param _versionPassed - The version passed into the insertion - * @param _storedDeadline - The deadline stored in the entry - * @param _deadlinePassed - The deadline passed into the insertion - */ - function _errIncompatibleEntryArguments( - bytes32 _entryKey, - uint64 _storedFee, - uint64 _feePassed, - uint32 _storedVersion, - uint32 _versionPassed, - uint32 _storedDeadline, - uint32 _deadlinePassed - ) internal pure { - revert Errors.Inbox__IncompatibleEntryArguments( - _entryKey, - _storedFee, - _feePassed, - _storedVersion, - _versionPassed, - _storedDeadline, - _deadlinePassed - ); + return root; } } diff --git a/l1-contracts/src/core/messagebridge/NewInbox.sol b/l1-contracts/src/core/messagebridge/NewInbox.sol deleted file mode 100644 index ddf618a5956..00000000000 --- a/l1-contracts/src/core/messagebridge/NewInbox.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Aztec Labs. -pragma solidity >=0.8.18; - -// Interfaces -import {IFrontier} from "../interfaces/messagebridge/IFrontier.sol"; -import {IRegistry} from "../interfaces/messagebridge/IRegistry.sol"; -import {INewInbox} from "../interfaces/messagebridge/INewInbox.sol"; - -// Libraries -import {Constants} from "../libraries/ConstantsGen.sol"; -import {DataStructures} from "../libraries/DataStructures.sol"; -import {Errors} from "../libraries/Errors.sol"; -import {Hash} from "../libraries/Hash.sol"; - -// Contracts -import {FrontierMerkle} from "./frontier_tree/Frontier.sol"; - -/** - * @title Inbox - * @author Aztec Labs - * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. - */ -// TODO: rename to Inbox once all the pieces of the new message model are in place. -contract NewInbox is INewInbox { - using Hash for DataStructures.L1ToL2Msg; - - address public immutable ROLLUP; - - uint256 internal immutable HEIGHT; - uint256 internal immutable SIZE; - bytes32 internal immutable EMPTY_ROOT; // The root of an empty frontier tree - - // Number of a tree which is ready to be consumed - uint256 public toConsume = Constants.INITIAL_L2_BLOCK_NUM; - // Number of a tree which is currently being filled - uint256 public inProgress = Constants.INITIAL_L2_BLOCK_NUM + 1; - - mapping(uint256 blockNumber => IFrontier tree) internal trees; - - constructor(address _rollup, uint256 _height) { - ROLLUP = _rollup; - - HEIGHT = _height; - SIZE = 2 ** _height; - - // We deploy the first tree - IFrontier firstTree = IFrontier(new FrontierMerkle(_height)); - trees[inProgress] = firstTree; - - EMPTY_ROOT = firstTree.root(); - } - - /** - * @notice Inserts a new message into the Inbox - * @dev Emits `LeafInserted` with data for easy access by the sequencer - * @param _recipient - The recipient of the message - * @param _content - The content of the message (application specific) - * @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) - * @return The key of the message in the set - */ - function sendL2Message( - DataStructures.L2Actor memory _recipient, - bytes32 _content, - bytes32 _secretHash - ) external override(INewInbox) returns (bytes32) { - if (uint256(_recipient.actor) > Constants.MAX_FIELD_VALUE) { - revert Errors.Inbox__ActorTooLarge(_recipient.actor); - } - if (uint256(_content) > Constants.MAX_FIELD_VALUE) { - revert Errors.Inbox__ContentTooLarge(_content); - } - if (uint256(_secretHash) > Constants.MAX_FIELD_VALUE) { - revert Errors.Inbox__SecretHashTooLarge(_secretHash); - } - - IFrontier currentTree = trees[inProgress]; - if (currentTree.isFull()) { - inProgress += 1; - currentTree = IFrontier(new FrontierMerkle(HEIGHT)); - trees[inProgress] = currentTree; - } - - DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ - sender: DataStructures.L1Actor(msg.sender, block.chainid), - recipient: _recipient, - content: _content, - secretHash: _secretHash, - // TODO(#4833): nuke the following 2 values from the struct once the new message model is in place - deadline: type(uint32).max, - fee: 0 - }); - - bytes32 leaf = message.sha256ToField(); - uint256 index = currentTree.insertLeaf(leaf); - emit LeafInserted(inProgress, index, leaf); - - return leaf; - } - - /** - * @notice Consumes the current tree, and starts a new one if needed - * @dev Only callable by the rollup contract - * @dev In the first iteration we return empty tree root because first block's messages tree is always - * empty because there has to be a 1 block lag to prevent sequencer DOS attacks - * @return The root of the consumed tree - */ - function consume() external override(INewInbox) returns (bytes32) { - if (msg.sender != ROLLUP) { - revert Errors.Inbox__Unauthorized(); - } - - bytes32 root = EMPTY_ROOT; - if (toConsume > Constants.INITIAL_L2_BLOCK_NUM) { - root = trees[toConsume].root(); - } - - // If we are "catching up" we skip the tree creation as it is already there - if (toConsume + 1 == inProgress) { - inProgress += 1; - trees[inProgress] = IFrontier(new FrontierMerkle(HEIGHT)); - } - - toConsume += 1; - - return root; - } -} diff --git a/l1-contracts/test/Inbox.t.sol b/l1-contracts/test/Inbox.t.sol index a120aa0bb39..b98d7bc2abd 100644 --- a/l1-contracts/test/Inbox.t.sol +++ b/l1-contracts/test/Inbox.t.sol @@ -3,39 +3,29 @@ pragma solidity >=0.8.18; import {Test} from "forge-std/Test.sol"; -import {IInbox} from "../src/core/interfaces/messagebridge/IInbox.sol"; -import {Inbox} from "../src/core/messagebridge/Inbox.sol"; -import {Registry} from "../src/core/messagebridge/Registry.sol"; + +import {InboxHarness} from "./harnesses/InboxHarness.sol"; import {Constants} from "../src/core/libraries/ConstantsGen.sol"; import {Errors} from "../src/core/libraries/Errors.sol"; - +import {Hash} from "../src/core/libraries/Hash.sol"; import {DataStructures} from "../src/core/libraries/DataStructures.sol"; -import {MessageBox} from "../src/core/libraries/MessageBox.sol"; contract InboxTest is Test { - event MessageAdded( - bytes32 indexed entryKey, - address indexed sender, - bytes32 indexed recipient, - uint256 senderChainId, - uint256 recipientVersion, - uint32 deadline, - uint64 fee, - bytes32 content, - bytes32 secretHash - ); - - event L1ToL2MessageCancelled(bytes32 indexed entryKey); - - Registry internal registry; - Inbox internal inbox; + using Hash for DataStructures.L1ToL2Msg; + + uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_L2_BLOCK_NUM + 1; + + InboxHarness internal inbox; uint256 internal version = 0; + bytes32 internal emptyTreeRoot; + + event LeafInserted(uint256 indexed blockNumber, uint256 index, bytes32 value); function setUp() public { address rollup = address(this); - registry = new Registry(); - inbox = new Inbox(address(registry)); - version = registry.upgrade(rollup, address(inbox), address(0x0)); + // We set low depth (5) to ensure we sufficiently test the tree transitions + inbox = new InboxHarness(rollup, 5); + emptyTreeRoot = inbox.getEmptyRoot(); } function _fakeMessage() internal view returns (DataStructures.L1ToL2Msg memory) { @@ -46,66 +36,72 @@ contract InboxTest is Test { version: version }), content: 0x2000000000000000000000000000000000000000000000000000000000000000, - secretHash: 0x3000000000000000000000000000000000000000000000000000000000000000, - fee: 5, - deadline: uint32(block.timestamp + 100) + secretHash: 0x3000000000000000000000000000000000000000000000000000000000000000 }); } - function testFuzzSendL2Msg(DataStructures.L1ToL2Msg memory _message) public { - // fix message.sender and deadline: + function _divideAndRoundUp(uint256 a, uint256 b) internal pure returns (uint256) { + return (a + b - 1) / b; + } + + function _boundMessage(DataStructures.L1ToL2Msg memory _message) + internal + view + returns (DataStructures.L1ToL2Msg memory) + { + // fix message.sender _message.sender = DataStructures.L1Actor({actor: address(this), chainId: block.chainid}); // ensure actor fits in a field _message.recipient.actor = bytes32(uint256(_message.recipient.actor) % Constants.P); - if (_message.deadline <= block.timestamp) { - _message.deadline = uint32(block.timestamp + 100); - } // ensure content fits in a field _message.content = bytes32(uint256(_message.content) % Constants.P); // ensure secret hash fits in a field _message.secretHash = bytes32(uint256(_message.secretHash) % Constants.P); - bytes32 expectedEntryKey = inbox.computeEntryKey(_message); + // update version + _message.recipient.version = version; + + return _message; + } + + // Since there is a 1 block lag between tree to be consumed and tree in progress the following invariant should never + // be violated + modifier checkInvariant() { + _; + assertLt(inbox.toConsume(), inbox.inProgress()); + } + + function testRevertIfNotConsumingFromRollup() public { + vm.prank(address(0x1)); + vm.expectRevert(Errors.Inbox__Unauthorized.selector); + inbox.consume(); + } + + function testFuzzInsert(DataStructures.L1ToL2Msg memory _message) public checkInvariant { + DataStructures.L1ToL2Msg memory message = _boundMessage(_message); + + bytes32 leaf = message.sha256ToField(); vm.expectEmit(true, true, true, true); // event we expect - emit MessageAdded( - expectedEntryKey, - _message.sender.actor, - _message.recipient.actor, - _message.sender.chainId, - _message.recipient.version, - _message.deadline, - _message.fee, - _message.content, - _message.secretHash - ); + emit LeafInserted(FIRST_REAL_TREE_NUM, 0, leaf); // event we will get - bytes32 entryKey = inbox.sendL2Message{value: _message.fee}( - _message.recipient, _message.deadline, _message.content, _message.secretHash - ); - assertEq(entryKey, expectedEntryKey); - DataStructures.Entry memory entry = inbox.get(entryKey); - assertEq(entry.count, 1); - assertEq(entry.fee, _message.fee); - assertEq(entry.deadline, _message.deadline); + bytes32 insertedLeaf = + inbox.sendL2Message(message.recipient, message.content, message.secretHash); + + assertEq(insertedLeaf, leaf); } - function testSendMultipleSameL2Messages() public { + function testSendDuplicateL2Messages() public checkInvariant { DataStructures.L1ToL2Msg memory message = _fakeMessage(); - bytes32 entryKey1 = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); - bytes32 entryKey2 = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); - bytes32 entryKey3 = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); + bytes32 leaf1 = inbox.sendL2Message(message.recipient, message.content, message.secretHash); + bytes32 leaf2 = inbox.sendL2Message(message.recipient, message.content, message.secretHash); + bytes32 leaf3 = inbox.sendL2Message(message.recipient, message.content, message.secretHash); - assertEq(entryKey1, entryKey2); - assertEq(entryKey2, entryKey3); - assertEq(inbox.get(entryKey1).count, 3); - assertEq(inbox.get(entryKey1).fee, 5); - assertEq(inbox.get(entryKey1).deadline, message.deadline); + // Only 1 tree should be non-zero + assertEq(inbox.getNumTrees(), 1); + + // All the leaves should be the same + assertEq(leaf1, leaf2); + assertEq(leaf2, leaf3); } function testRevertIfActorTooLarge() public { @@ -114,18 +110,14 @@ contract InboxTest is Test { vm.expectRevert( abi.encodeWithSelector(Errors.Inbox__ActorTooLarge.selector, message.recipient.actor) ); - inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); + inbox.sendL2Message(message.recipient, message.content, message.secretHash); } function testRevertIfContentTooLarge() public { DataStructures.L1ToL2Msg memory message = _fakeMessage(); message.content = bytes32(Constants.MAX_FIELD_VALUE + 1); vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__ContentTooLarge.selector, message.content)); - inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); + inbox.sendL2Message(message.recipient, message.content, message.secretHash); } function testRevertIfSecretHashTooLarge() public { @@ -134,174 +126,73 @@ contract InboxTest is Test { vm.expectRevert( abi.encodeWithSelector(Errors.Inbox__SecretHashTooLarge.selector, message.secretHash) ); - inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); + inbox.sendL2Message(message.recipient, message.content, message.secretHash); } - function testRevertIfCancellingMessageFromDifferentAddress() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); - vm.prank(address(0x1)); - vm.expectRevert(Errors.Inbox__Unauthorized.selector); - inbox.cancelL2Message(message, address(0x1)); - } + function testFuzzSendAndConsume( + DataStructures.L1ToL2Msg[] memory _messagesFirstBatch, + DataStructures.L1ToL2Msg[] memory _messagesSecondBatch, + uint256 _numTreesToConsumeFirstBatch, + uint256 _numTreesToConsumeSecondBatch + ) public { + // Send first batch of messages + _send(_messagesFirstBatch); - function testRevertIfCancellingMessageWhenDeadlineHasntPassed() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - message.deadline = uint32(block.timestamp + 1000); - inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); - skip(500); // deadline = 1000. block.timestamp = 500. Not cancellable: - vm.expectRevert(Errors.Inbox__NotPastDeadline.selector); - inbox.cancelL2Message(message, address(0x1)); - } + // Consume first few trees + _consume(_numTreesToConsumeFirstBatch); - function testRevertIfCancellingNonExistentMessage() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - bytes32 entryKey = inbox.computeEntryKey(message); - skip(500); // make message cancellable. - vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, entryKey)); - inbox.cancelL2Message(message, address(0x1)); + // Send second batch of messages + _send(_messagesSecondBatch); + + // Consume second batch of trees + _consume(_numTreesToConsumeSecondBatch); } - function testCancelMessage() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - address feeCollector = address(0x1); - bytes32 expectedEntryKey = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); - skip(500); // make message cancellable. + function _send(DataStructures.L1ToL2Msg[] memory _messages) internal checkInvariant { + bytes32 toConsumeRoot = inbox.getToConsumeRoot(); - vm.expectEmit(true, false, false, false); - // event we expect - emit L1ToL2MessageCancelled(expectedEntryKey); - // event we will get - inbox.cancelL2Message(message, feeCollector); - // fees accrued as expected: - assertEq(inbox.feesAccrued(feeCollector), message.fee); + // We send the messages and then check that toConsume root did not change. + for (uint256 i = 0; i < _messages.length; i++) { + DataStructures.L1ToL2Msg memory message = _boundMessage(_messages[i]); - // no such message to consume: - bytes32[] memory entryKeys = new bytes32[](1); - entryKeys[0] = expectedEntryKey; - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedEntryKey) - ); - inbox.batchConsume(entryKeys, feeCollector); - } + // We check whether a new tree is correctly initialized when the one in progress is full + uint256 numTrees = inbox.getNumTrees(); + uint256 expectedNumTrees = inbox.treeInProgressFull() ? numTrees + 1 : numTrees; - function testRevertIfNotConsumingFromRollup() public { - vm.prank(address(0x1)); - bytes32[] memory entryKeys = new bytes32[](1); - entryKeys[0] = bytes32("random"); - vm.expectRevert( - abi.encodeWithSelector(Errors.Registry__RollupNotRegistered.selector, address(1)) - ); - inbox.batchConsume(entryKeys, address(0x1)); - } + inbox.sendL2Message(message.recipient, message.content, message.secretHash); - function testRevertIfOneKeyIsPastDeadlineWhenBatchConsuming() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - bytes32 entryKey1 = inbox.sendL2Message{value: message.fee}( - message.recipient, uint32(block.timestamp + 200), message.content, message.secretHash - ); - bytes32 entryKey2 = inbox.sendL2Message{value: message.fee}( - message.recipient, uint32(block.timestamp + 100), message.content, message.secretHash - ); - bytes32 entryKey3 = inbox.sendL2Message{value: message.fee}( - message.recipient, uint32(block.timestamp + 300), message.content, message.secretHash - ); - bytes32[] memory entryKeys = new bytes32[](3); - entryKeys[0] = entryKey1; - entryKeys[1] = entryKey2; - entryKeys[2] = entryKey3; - - skip(150); // block.timestamp now +150 ms. entryKey2 is past deadline - vm.expectRevert(Errors.Inbox__PastDeadline.selector); - inbox.batchConsume(entryKeys, address(0x1)); - } - - function testFuzzRevertIfConsumingAMessageThatDoesntExist(bytes32 _entryKey) public { - bytes32[] memory entryKeys = new bytes32[](1); - if (_entryKey == bytes32(0)) { - entryKeys[0] = bytes32("random"); - } else { - entryKeys[0] = _entryKey; + assertEq(inbox.getNumTrees(), expectedNumTrees, "Unexpected number of trees"); } - vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, entryKeys[0])); - inbox.batchConsume(entryKeys, address(0x1)); - } - function testRevertIfConsumingTheSameMessageMoreThanTheCountOfEntries() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - address feeCollector = address(0x1); - bytes32 entryKey = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash + // Root of a tree waiting to be consumed should not change because we introduced a 1 block lag to prevent sequencer + // DOS attacks + assertEq( + inbox.getToConsumeRoot(), + toConsumeRoot, + "Root of a tree waiting to be consumed should not change" ); - bytes32[] memory entryKeys = new bytes32[](1); - entryKeys[0] = entryKey; - - inbox.batchConsume(entryKeys, feeCollector); - assertEq(inbox.feesAccrued(feeCollector), message.fee); - - // consuming this again should fail: - vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, entryKeys[0])); - inbox.batchConsume(entryKeys, feeCollector); } - function testRevertIfConsumingFromWrongRollup() public { - address wrongRollup = address(0xbeeffeed); - uint256 wrongVersion = registry.upgrade(wrongRollup, address(inbox), address(0x0)); + function _consume(uint256 _numTreesToConsume) internal checkInvariant { + uint256 initialNumTrees = inbox.getNumTrees(); + // We use (initialNumTrees * 2) as upper bound here because we want to test the case where we go beyond + // the currently initalized number of trees. When consuming the newly initialized trees we should get zero roots. + uint256 numTreesToConsume = bound(_numTreesToConsume, 1, initialNumTrees * 2); - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - address feeCollector = address(0x1); - bytes32 entryKey = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); - bytes32[] memory entryKeys = new bytes32[](1); - entryKeys[0] = entryKey; - - vm.prank(wrongRollup); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__InvalidVersion.selector, version, wrongVersion) - ); - inbox.batchConsume(entryKeys, feeCollector); - } + // Now we consume the trees + for (uint256 i = 0; i < numTreesToConsume; i++) { + uint256 numTrees = inbox.getNumTrees(); + uint256 expectedNumTrees = + (inbox.toConsume() + 1 == inbox.inProgress()) ? numTrees + 1 : numTrees; + bytes32 root = inbox.consume(); - function testFuzzBatchConsume(DataStructures.L1ToL2Msg[] memory _messages) public { - bytes32[] memory entryKeys = new bytes32[](_messages.length); - uint256 expectedTotalFee = 0; - address feeCollector = address(0x1); + // We check whether a new tree is correctly initialized when the one which was in progress was set as to consume + assertEq(inbox.getNumTrees(), expectedNumTrees, "Unexpected number of trees"); - // insert messages: - for (uint256 i = 0; i < _messages.length; i++) { - DataStructures.L1ToL2Msg memory message = _messages[i]; - // fix message.sender and deadline to be more than current time: - message.sender = DataStructures.L1Actor({actor: address(this), chainId: block.chainid}); - // ensure actor fits in a field - message.recipient.actor = bytes32(uint256(message.recipient.actor) % Constants.P); - if (message.deadline <= block.timestamp) { - message.deadline = uint32(block.timestamp + 100); + // If we go beyong the number of trees initialized before consuming we should get empty root + if (i > initialNumTrees) { + assertEq(root, emptyTreeRoot, "Root of a newly initialized tree not empty"); } - // ensure content fits in a field - message.content = bytes32(uint256(message.content) % Constants.P); - // ensure secret hash fits in a field - message.secretHash = bytes32(uint256(message.secretHash) % Constants.P); - // update version - message.recipient.version = version; - expectedTotalFee += message.fee; - entryKeys[i] = inbox.sendL2Message{value: message.fee}( - message.recipient, message.deadline, message.content, message.secretHash - ); } - - // batch consume: - inbox.batchConsume(entryKeys, feeCollector); - - // fees accrued as expected: - assertEq(inbox.feesAccrued(feeCollector), expectedTotalFee); } } diff --git a/l1-contracts/test/NewInbox.t.sol b/l1-contracts/test/NewInbox.t.sol deleted file mode 100644 index 869070d692f..00000000000 --- a/l1-contracts/test/NewInbox.t.sol +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Aztec Labs. -pragma solidity >=0.8.18; - -import {Test} from "forge-std/Test.sol"; - -import {NewInboxHarness} from "./harnesses/NewInboxHarness.sol"; -import {Constants} from "../src/core/libraries/ConstantsGen.sol"; -import {Errors} from "../src/core/libraries/Errors.sol"; -import {Hash} from "../src/core/libraries/Hash.sol"; -import {DataStructures} from "../src/core/libraries/DataStructures.sol"; - -contract NewInboxTest is Test { - using Hash for DataStructures.L1ToL2Msg; - - uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_L2_BLOCK_NUM + 1; - - NewInboxHarness internal inbox; - uint256 internal version = 0; - bytes32 internal emptyTreeRoot; - - event LeafInserted(uint256 indexed blockNumber, uint256 index, bytes32 value); - - function setUp() public { - address rollup = address(this); - // We set low depth (5) to ensure we sufficiently test the tree transitions - inbox = new NewInboxHarness(rollup, 5); - emptyTreeRoot = inbox.getEmptyRoot(); - } - - function _fakeMessage() internal view returns (DataStructures.L1ToL2Msg memory) { - return DataStructures.L1ToL2Msg({ - sender: DataStructures.L1Actor({actor: address(this), chainId: block.chainid}), - recipient: DataStructures.L2Actor({ - actor: 0x1000000000000000000000000000000000000000000000000000000000000000, - version: version - }), - content: 0x2000000000000000000000000000000000000000000000000000000000000000, - secretHash: 0x3000000000000000000000000000000000000000000000000000000000000000, - fee: 0, - deadline: type(uint32).max - }); - } - - function _divideAndRoundUp(uint256 a, uint256 b) internal pure returns (uint256) { - return (a + b - 1) / b; - } - - function _boundMessage(DataStructures.L1ToL2Msg memory _message) - internal - view - returns (DataStructures.L1ToL2Msg memory) - { - // fix message.sender and deadline to be more than current time: - _message.sender = DataStructures.L1Actor({actor: address(this), chainId: block.chainid}); - // ensure actor fits in a field - _message.recipient.actor = bytes32(uint256(_message.recipient.actor) % Constants.P); - // ensure content fits in a field - _message.content = bytes32(uint256(_message.content) % Constants.P); - // ensure secret hash fits in a field - _message.secretHash = bytes32(uint256(_message.secretHash) % Constants.P); - // update version - _message.recipient.version = version; - - // TODO(#4833): nuke the following 2 values from the struct once the new message model is in place - _message.deadline = type(uint32).max; - _message.fee = 0; - - return _message; - } - - // Since there is a 1 block lag between tree to be consumed and tree in progress the following invariant should never - // be violated - modifier checkInvariant() { - _; - assertLt(inbox.toConsume(), inbox.inProgress()); - } - - function testRevertIfNotConsumingFromRollup() public { - vm.prank(address(0x1)); - vm.expectRevert(Errors.Inbox__Unauthorized.selector); - inbox.consume(); - } - - function testFuzzInsert(DataStructures.L1ToL2Msg memory _message) public checkInvariant { - DataStructures.L1ToL2Msg memory message = _boundMessage(_message); - - bytes32 leaf = message.sha256ToField(); - vm.expectEmit(true, true, true, true); - // event we expect - emit LeafInserted(FIRST_REAL_TREE_NUM, 0, leaf); - // event we will get - bytes32 insertedLeaf = - inbox.sendL2Message(message.recipient, message.content, message.secretHash); - - assertEq(insertedLeaf, leaf); - } - - function testSendMultipleSameL2Messages() public checkInvariant { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - bytes32 leaf1 = inbox.sendL2Message(message.recipient, message.content, message.secretHash); - bytes32 leaf2 = inbox.sendL2Message(message.recipient, message.content, message.secretHash); - bytes32 leaf3 = inbox.sendL2Message(message.recipient, message.content, message.secretHash); - - // Only 1 tree should be non-zero - assertEq(inbox.getNumTrees(), 1); - - // All the leaves should be the same - assertEq(leaf1, leaf2); - assertEq(leaf2, leaf3); - } - - function testRevertIfActorTooLarge() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - message.recipient.actor = bytes32(Constants.MAX_FIELD_VALUE + 1); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__ActorTooLarge.selector, message.recipient.actor) - ); - inbox.sendL2Message(message.recipient, message.content, message.secretHash); - } - - function testRevertIfContentTooLarge() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - message.content = bytes32(Constants.MAX_FIELD_VALUE + 1); - vm.expectRevert(abi.encodeWithSelector(Errors.Inbox__ContentTooLarge.selector, message.content)); - inbox.sendL2Message(message.recipient, message.content, message.secretHash); - } - - function testRevertIfSecretHashTooLarge() public { - DataStructures.L1ToL2Msg memory message = _fakeMessage(); - message.secretHash = bytes32(Constants.MAX_FIELD_VALUE + 1); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__SecretHashTooLarge.selector, message.secretHash) - ); - inbox.sendL2Message(message.recipient, message.content, message.secretHash); - } - - function testFuzzSendAndConsume( - DataStructures.L1ToL2Msg[] memory _messagesFirstBatch, - DataStructures.L1ToL2Msg[] memory _messagesSecondBatch, - uint256 _numTreesToConsumeFirstBatch, - uint256 _numTreesToConsumeSecondBatch - ) public { - // Send first batch of messages - _send(_messagesFirstBatch); - - // Consume first few trees - _consume(_numTreesToConsumeFirstBatch); - - // Send second batch of messages - _send(_messagesSecondBatch); - - // Consume second batch of trees - _consume(_numTreesToConsumeSecondBatch); - } - - function _send(DataStructures.L1ToL2Msg[] memory _messages) internal checkInvariant { - bytes32 toConsumeRoot = inbox.getToConsumeRoot(); - - // We send the messages and then check that toConsume root did not change. - for (uint256 i = 0; i < _messages.length; i++) { - DataStructures.L1ToL2Msg memory message = _boundMessage(_messages[i]); - - // We check whether a new tree is correctly initialized when the one in progress is full - uint256 numTrees = inbox.getNumTrees(); - uint256 expectedNumTrees = inbox.treeInProgressFull() ? numTrees + 1 : numTrees; - - inbox.sendL2Message(message.recipient, message.content, message.secretHash); - - assertEq(inbox.getNumTrees(), expectedNumTrees, "Unexpected number of trees"); - } - - // Root of a tree waiting to be consumed should not change because we introduced a 1 block lag to prevent sequencer - // DOS attacks - assertEq( - inbox.getToConsumeRoot(), - toConsumeRoot, - "Root of a tree waiting to be consumed should not change" - ); - } - - function _consume(uint256 _numTreesToConsume) internal checkInvariant { - uint256 initialNumTrees = inbox.getNumTrees(); - // We use (initialNumTrees * 2) as upper bound here because we want to test the case where we go beyond - // the currently initalized number of trees. When consuming the newly initialized trees we should get zero roots. - uint256 numTreesToConsume = bound(_numTreesToConsume, 1, initialNumTrees * 2); - - // Now we consume the trees - for (uint256 i = 0; i < numTreesToConsume; i++) { - uint256 numTrees = inbox.getNumTrees(); - uint256 expectedNumTrees = - (inbox.toConsume() + 1 == inbox.inProgress()) ? numTrees + 1 : numTrees; - bytes32 root = inbox.consume(); - - // We check whether a new tree is correctly initialized when the one which was in progress was set as to consume - assertEq(inbox.getNumTrees(), expectedNumTrees, "Unexpected number of trees"); - - // If we go beyong the number of trees initialized before consuming we should get empty root - if (i > initialNumTrees) { - assertEq(root, emptyTreeRoot, "Root of a newly initialized tree not empty"); - } - } - } -} diff --git a/l1-contracts/test/Registry.t.sol b/l1-contracts/test/Registry.t.sol index 6d844c478af..49a6921f376 100644 --- a/l1-contracts/test/Registry.t.sol +++ b/l1-contracts/test/Registry.t.sol @@ -36,18 +36,18 @@ contract RegistryTest is Test { function testUpgrade() public { address newRollup = address(0xbeef1); - address newInbox = address(0xbeef2); + address inbox = address(0xbeef2); address newOutbox = address(0xbeef3); - uint256 newVersion = registry.upgrade(newRollup, newInbox, newOutbox); + uint256 newVersion = registry.upgrade(newRollup, inbox, newOutbox); assertEq(registry.numberOfVersions(), 2, "should have 2 versions"); DataStructures.RegistrySnapshot memory snapshot = registry.getCurrentSnapshot(); assertEq(snapshot.rollup, newRollup, "should have newRollup"); - assertEq(snapshot.inbox, newInbox, "should have newInbox"); + assertEq(snapshot.inbox, inbox, "should have inbox"); assertEq(snapshot.outbox, newOutbox, "should have newOutbox"); assertEq(address(registry.getRollup()), newRollup); - assertEq(address(registry.getInbox()), newInbox); + assertEq(address(registry.getInbox()), inbox); assertEq(address(registry.getOutbox()), newOutbox); assertEq( registry.getVersionFor(newRollup), newVersion, "should have version newVersion for newRollup" diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index b8ed7ceaa63..540b23d69a8 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -8,7 +8,6 @@ import {DataStructures} from "../src/core/libraries/DataStructures.sol"; import {Registry} from "../src/core/messagebridge/Registry.sol"; import {Inbox} from "../src/core/messagebridge/Inbox.sol"; -import {NewInbox} from "../src/core/messagebridge/NewInbox.sol"; import {Outbox} from "../src/core/messagebridge/Outbox.sol"; import {Errors} from "../src/core/libraries/Errors.sol"; import {Rollup} from "../src/core/Rollup.sol"; @@ -23,16 +22,14 @@ contract RollupTest is DecoderBase { Inbox internal inbox; Outbox internal outbox; Rollup internal rollup; - NewInbox internal newInbox; AvailabilityOracle internal availabilityOracle; function setUp() public virtual { registry = new Registry(); - inbox = new Inbox(address(registry)); outbox = new Outbox(address(registry)); availabilityOracle = new AvailabilityOracle(); rollup = new Rollup(registry, availabilityOracle); - newInbox = NewInbox(address(rollup.NEW_INBOX())); + inbox = Inbox(address(rollup.INBOX())); registry.upgrade(address(rollup), address(inbox), address(outbox)); } @@ -131,21 +128,14 @@ contract RollupTest is DecoderBase { _populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content); - for (uint256 i = 0; i < full.messages.l1ToL2Messages.length; i++) { - if (full.messages.l1ToL2Messages[i] == bytes32(0)) { - continue; - } - assertTrue(inbox.contains(full.messages.l1ToL2Messages[i]), "msg not in inbox"); - } - availabilityOracle.publish(body); - uint256 toConsume = newInbox.toConsume(); + uint256 toConsume = inbox.toConsume(); vm.record(); rollup.process(header, archive, body, bytes("")); - assertEq(newInbox.toConsume(), toConsume + 1, "Message subtree not consumed"); + assertEq(inbox.toConsume(), toConsume + 1, "Message subtree not consumed"); (, bytes32[] memory inboxWrites) = vm.accesses(address(inbox)); (, bytes32[] memory outboxWrites) = vm.accesses(address(outbox)); @@ -162,31 +152,13 @@ contract RollupTest is DecoderBase { assertEq(outboxWrites.length, count, "Invalid outbox writes"); } - { - uint256 count = 0; - for (uint256 i = 0; i < full.messages.l1ToL2Messages.length; i++) { - if (full.messages.l1ToL2Messages[i] == bytes32(0)) { - continue; - } - assertFalse(inbox.contains(full.messages.l1ToL2Messages[i]), "msg not consumed"); - count++; - } - assertEq(inboxWrites.length, count, "Invalid inbox writes"); - } - assertEq(rollup.archive(), archive, "Invalid archive"); } function _populateInbox(address _sender, bytes32 _recipient, bytes32[] memory _contents) internal { - uint32 deadline = type(uint32).max; for (uint256 i = 0; i < _contents.length; i++) { vm.prank(_sender); inbox.sendL2Message( - DataStructures.L2Actor({actor: _recipient, version: 1}), deadline, _contents[i], bytes32(0) - ); - - vm.prank(_sender); - newInbox.sendL2Message( DataStructures.L2Actor({actor: _recipient, version: 1}), _contents[i], bytes32(0) ); } diff --git a/l1-contracts/test/decoders/Base.sol b/l1-contracts/test/decoders/Base.sol index 4d2b9a85e9b..5a07db0e8b6 100644 --- a/l1-contracts/test/decoders/Base.sol +++ b/l1-contracts/test/decoders/Base.sol @@ -29,7 +29,6 @@ contract DecoderBase is Test { } struct Messages { - bytes32[] l1ToL2Messages; bytes32[] l2ToL1Messages; } @@ -38,7 +37,6 @@ contract DecoderBase is Test { bytes body; DecodedHeader decodedHeader; bytes header; - bytes32 l1ToL2MessagesHash; bytes32 publicInputsHash; bytes32 txsEffectsHash; } diff --git a/l1-contracts/test/decoders/Decoders.t.sol b/l1-contracts/test/decoders/Decoders.t.sol index 6227407d6ba..96374f7ccaf 100644 --- a/l1-contracts/test/decoders/Decoders.t.sol +++ b/l1-contracts/test/decoders/Decoders.t.sol @@ -154,25 +154,10 @@ contract DecodersTest is DecoderBase { // Messages { - ( - bytes32 msgsInHash, - bytes32 msgsL2ToL1MsgsHash, - bytes32[] memory msgsL1ToL2Msgs, - bytes32[] memory msgsL2ToL1Msgs - ) = messagesHelper.decode(data.block.body); - - assertEq(msgsInHash, data.block.l1ToL2MessagesHash, "Invalid l1ToL2MsgsHash msgs"); + (,,, bytes32[] memory msgsL2ToL1Msgs) = messagesHelper.decode(data.block.body); // assertEq(msgsL2ToL1MsgsHash, b.l2ToL1MessagesHash, "Invalid l2ToL1MsgsHash"); - // L1 -> L2 messages - assertEq( - msgsL1ToL2Msgs.length, data.messages.l1ToL2Messages.length, "Invalid l1ToL2Msgs length" - ); - for (uint256 i = 0; i < msgsL1ToL2Msgs.length; i++) { - assertEq(msgsL1ToL2Msgs[i], data.messages.l1ToL2Messages[i], "Invalid l1ToL2Msgs messages"); - } - // L2 -> L1 messages assertEq( msgsL2ToL1Msgs.length, data.messages.l2ToL1Messages.length, "Invalid l2ToL1Msgs length" diff --git a/l1-contracts/test/fixtures/empty_block_0.json b/l1-contracts/test/fixtures/empty_block_0.json index 6d0b5c1e213..4bd8877cd5e 100644 --- a/l1-contracts/test/fixtures/empty_block_0.json +++ b/l1-contracts/test/fixtures/empty_block_0.json @@ -5,24 +5,6 @@ "sender": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" }, "messages": { - "l1ToL2Messages": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], "l2ToL1Messages": [ "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -79,7 +61,6 @@ } }, "header": "0x012a86560737adb075e12af8253fb09abf17aa841fb56d180bc89f0d2d473c7f0000000100000000000000000000000000000000000000000000000000000000000000029139297703640b243028d35c29ae8c0667886c4edc8db5f879c260d2051bb8a9536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123cc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c1864fcdaa80ff2719154fa7c8a9050662972707168d69eac9db6fd3110829f800000001016642d9ccd8346c403aa4c3fa451178b22534a27035cdaa6ec34ae53b29c50cb000001000bcfa3e9f1a8922ee92c6dc964d6595907c1804a86753774322b468f69d4f278000001800572c8db882674dd026b8877fbba1b700a4407da3ae9ce5fa43215a28163362b000000c00000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000066440eb666440eb666440eb666440eb666440eb6061ca689507c7f1ccc68c2ad086c9d5d94f50869cb1b718c6a46aaf77a4500ba", - "l1ToL2MessagesHash": "0x076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560", "publicInputsHash": "0x2a2aa21195442355cb37f9b6f1f7099d2e93c465fd01ad5df56c4aee474cd768" } } \ No newline at end of file diff --git a/l1-contracts/test/fixtures/empty_block_1.json b/l1-contracts/test/fixtures/empty_block_1.json index 9f684f3ea7d..365250d8b15 100644 --- a/l1-contracts/test/fixtures/empty_block_1.json +++ b/l1-contracts/test/fixtures/empty_block_1.json @@ -5,24 +5,6 @@ "sender": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" }, "messages": { - "l1ToL2Messages": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], "l2ToL1Messages": [ "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -35,7 +17,7 @@ ] }, "block": { - "archive": "0x2cff40994bd00149d898c0c92ad0c5713b04077e2f8d150f27febd4fbfeac114", + "archive": "0x238a40ee6986fb289ea016382ae1bcb6f182b5c6f150528bdf06b90d0c64aca6", "body": "0x00000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "txsEffectsHash": "0x9139297703640b243028d35c29ae8c0667886c4edc8db5f879c260d2051bb8a9", "decodedHeader": { @@ -48,7 +30,7 @@ "globalVariables": { "blockNumber": 2, "chainId": 31337, - "timestamp": 1710325403, + "timestamp": 1710506416, "version": 1, "coinbase": "0x66440eb666440eb666440eb666440eb666440eb6", "feeRecipient": "0x061ca689507c7f1ccc68c2ad086c9d5d94f50869cb1b718c6a46aaf77a4500ba" @@ -78,8 +60,7 @@ } } }, - "header": "0x02c6f1a862fd5dbef12bdeccfcc2bcf18a5c5b26c0465ac6470bc2c84e1626950000000200000000000000000000000000000000000000000000000000000000000000029139297703640b243028d35c29ae8c0667886c4edc8db5f879c260d2051bb8a9536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123cc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c1864fcdaa80ff2719154fa7c8a9050662972707168d69eac9db6fd3110829f800000002016642d9ccd8346c403aa4c3fa451178b22534a27035cdaa6ec34ae53b29c50cb000002000bcfa3e9f1a8922ee92c6dc964d6595907c1804a86753774322b468f69d4f278000002800572c8db882674dd026b8877fbba1b700a4407da3ae9ce5fa43215a28163362b000001400000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000065f17e9b66440eb666440eb666440eb666440eb666440eb6061ca689507c7f1ccc68c2ad086c9d5d94f50869cb1b718c6a46aaf77a4500ba", - "l1ToL2MessagesHash": "0x076a27c79e5ace2a3d47f9dd2e83e4ff6ea8872b3c2218f66c92b89b55f36560", - "publicInputsHash": "0x151689d1af1478ba00ee16a4c21b6c4f61ecd7e1e71ad46870d5fd44c5759c23" + "header": "0x02c6f1a862fd5dbef12bdeccfcc2bcf18a5c5b26c0465ac6470bc2c84e1626950000000200000000000000000000000000000000000000000000000000000000000000029139297703640b243028d35c29ae8c0667886c4edc8db5f879c260d2051bb8a9536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123cc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c1864fcdaa80ff2719154fa7c8a9050662972707168d69eac9db6fd3110829f800000002016642d9ccd8346c403aa4c3fa451178b22534a27035cdaa6ec34ae53b29c50cb000002000bcfa3e9f1a8922ee92c6dc964d6595907c1804a86753774322b468f69d4f278000002800572c8db882674dd026b8877fbba1b700a4407da3ae9ce5fa43215a28163362b000001400000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000065f441b066440eb666440eb666440eb666440eb666440eb6061ca689507c7f1ccc68c2ad086c9d5d94f50869cb1b718c6a46aaf77a4500ba", + "publicInputsHash": "0x034ebbda6359ef928d35326c6111e1b0af41f736f13438cc2d32c64f352d41b0" } } \ No newline at end of file diff --git a/l1-contracts/test/fixtures/mixed_block_0.json b/l1-contracts/test/fixtures/mixed_block_0.json index 30ae66075ef..85f4afb95ff 100644 --- a/l1-contracts/test/fixtures/mixed_block_0.json +++ b/l1-contracts/test/fixtures/mixed_block_0.json @@ -22,24 +22,6 @@ "sender": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" }, "messages": { - "l1ToL2Messages": [ - "0x151de48ca3efbae39f180fe00b8f472ec9f25be10b4f283a87c6d78393537039", - "0x14c2ea9dedf77698d4afe23bc663263eed0bf9aa3a8b17d9b74812f185610f9e", - "0x1570cc6641699e3ae87fa258d80a6d853f7b8ccb211dc244d017e2ca6530f8a1", - "0x2806c860af67e9cd50000378411b8c4c4db172ceb2daa862b259b689ccbdc1e0", - "0x05f140c7c95624c8006774279a01ec1ea88617999e4fe6997b6576c4e1c7395a", - "0x22048b96b586596bd740d0402e15f5577f7ceb5496b65aafc6d89d7c3b34924b", - "0x0c3f2d50d16279970d682cada30bfa6b29bc0bac0ee2389f6a0444853eccaa93", - "0x2b2a60561da46a58569d71044a84c639e7f88429826e5622581536eb906d9cdd", - "0x25a2c0a76f7da6924e10751c755227d2535f4ad258b984e78f9f452a853c5230", - "0x0e212d8e2069e4254d81af07744bcbb81121a38f0e2dbed69a523d3fbf85b75c", - "0x287ca6f33aadbac2e4f058e05924c140d7895a6ed167caf804b710d2ae3ba62b", - "0x1b51297b3ea37637af6bd56cf33425d95cc5c96e9c2ee3077322fbec86a0c7f3", - "0x2c15d2a888c6cc122e99478c92470a1311635142d82ad7ae67410beeef4ae31f", - "0x0902ba2fb964922a4610bb18901f7b923885c1d034da5769a48203ae6f0206a9", - "0x2855e2c01ddb3d6553386b5580d681b8230fa4062948668f834f23e0636eaff7", - "0x0aaa64519aafdf4b040bd2f9836e76b9dc13cfec8065dcdf2834d786e06260d1" - ], "l2ToL1Messages": [ "0x0000000000000000000000000000000000000000000000000000000000000340", "0x0000000000000000000000000000000000000000000000000000000000000341", @@ -52,8 +34,8 @@ ] }, "block": { - "archive": "0x002112631bea3a8334e954f4de111c9158cafeab265fc94ee695b3b4d20f0427", - "body": "0x00000010151de48ca3efbae39f180fe00b8f472ec9f25be10b4f283a87c6d7839353703914c2ea9dedf77698d4afe23bc663263eed0bf9aa3a8b17d9b74812f185610f9e1570cc6641699e3ae87fa258d80a6d853f7b8ccb211dc244d017e2ca6530f8a12806c860af67e9cd50000378411b8c4c4db172ceb2daa862b259b689ccbdc1e005f140c7c95624c8006774279a01ec1ea88617999e4fe6997b6576c4e1c7395a22048b96b586596bd740d0402e15f5577f7ceb5496b65aafc6d89d7c3b34924b0c3f2d50d16279970d682cada30bfa6b29bc0bac0ee2389f6a0444853eccaa932b2a60561da46a58569d71044a84c639e7f88429826e5622581536eb906d9cdd25a2c0a76f7da6924e10751c755227d2535f4ad258b984e78f9f452a853c52300e212d8e2069e4254d81af07744bcbb81121a38f0e2dbed69a523d3fbf85b75c287ca6f33aadbac2e4f058e05924c140d7895a6ed167caf804b710d2ae3ba62b1b51297b3ea37637af6bd56cf33425d95cc5c96e9c2ee3077322fbec86a0c7f32c15d2a888c6cc122e99478c92470a1311635142d82ad7ae67410beeef4ae31f0902ba2fb964922a4610bb18901f7b923885c1d034da5769a48203ae6f0206a92855e2c01ddb3d6553386b5580d681b8230fa4062948668f834f23e0636eaff70aaa64519aafdf4b040bd2f9836e76b9dc13cfec8065dcdf2834d786e06260d100000004380000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000001420000000000000000000000000000000000000000000000000000000000000143000000000000000000000000000000000000000000000000000000000000014400000000000000000000000000000000000000000000000000000000000001450000000000000000000000000000000000000000000000000000000000000146000000000000000000000000000000000000000000000000000000000000014700000000000000000000000000000000000000000000000000000000000001480000000000000000000000000000000000000000000000000000000000000149000000000000000000000000000000000000000000000000000000000000014a000000000000000000000000000000000000000000000000000000000000014b000000000000000000000000000000000000000000000000000000000000014c000000000000000000000000000000000000000000000000000000000000014d000000000000000000000000000000000000000000000000000000000000014e000000000000000000000000000000000000000000000000000000000000014f0000000000000000000000000000000000000000000000000000000000000150000000000000000000000000000000000000000000000000000000000000015100000000000000000000000000000000000000000000000000000000000001520000000000000000000000000000000000000000000000000000000000000153000000000000000000000000000000000000000000000000000000000000015400000000000000000000000000000000000000000000000000000000000001550000000000000000000000000000000000000000000000000000000000000156000000000000000000000000000000000000000000000000000000000000015700000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000159000000000000000000000000000000000000000000000000000000000000015a000000000000000000000000000000000000000000000000000000000000015b000000000000000000000000000000000000000000000000000000000000015c000000000000000000000000000000000000000000000000000000000000015d000000000000000000000000000000000000000000000000000000000000015e000000000000000000000000000000000000000000000000000000000000015f0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000001620000000000000000000000000000000000000000000000000000000000000163000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000001650000000000000000000000000000000000000000000000000000000000000166000000000000000000000000000000000000000000000000000000000000016700000000000000000000000000000000000000000000000000000000000001680000000000000000000000000000000000000000000000000000000000000169000000000000000000000000000000000000000000000000000000000000016a000000000000000000000000000000000000000000000000000000000000016b000000000000000000000000000000000000000000000000000000000000016c000000000000000000000000000000000000000000000000000000000000016d000000000000000000000000000000000000000000000000000000000000016e000000000000000000000000000000000000000000000000000000000000016f00000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000171000000000000000000000000000000000000000000000000000000000000017200000000000000000000000000000000000000000000000000000000000001730000000000000000000000000000000000000000000000000000000000000174000000000000000000000000000000000000000000000000000000000000017500000000000000000000000000000000000000000000000000000000000001760000000000000000000000000000000000000000000000000000000000000177370000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024100000000000000000000000000000000000000000000000000000000000002420000000000000000000000000000000000000000000000000000000000000243000000000000000000000000000000000000000000000000000000000000024400000000000000000000000000000000000000000000000000000000000002450000000000000000000000000000000000000000000000000000000000000246000000000000000000000000000000000000000000000000000000000000024700000000000000000000000000000000000000000000000000000000000002480000000000000000000000000000000000000000000000000000000000000249000000000000000000000000000000000000000000000000000000000000024a000000000000000000000000000000000000000000000000000000000000024b000000000000000000000000000000000000000000000000000000000000024c000000000000000000000000000000000000000000000000000000000000024d000000000000000000000000000000000000000000000000000000000000024e000000000000000000000000000000000000000000000000000000000000024f0000000000000000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000000000000000000025100000000000000000000000000000000000000000000000000000000000002520000000000000000000000000000000000000000000000000000000000000253000000000000000000000000000000000000000000000000000000000000025400000000000000000000000000000000000000000000000000000000000002550000000000000000000000000000000000000000000000000000000000000256000000000000000000000000000000000000000000000000000000000000025700000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000259000000000000000000000000000000000000000000000000000000000000025a000000000000000000000000000000000000000000000000000000000000025b000000000000000000000000000000000000000000000000000000000000025c000000000000000000000000000000000000000000000000000000000000025d000000000000000000000000000000000000000000000000000000000000025e000000000000000000000000000000000000000000000000000000000000025f0000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026100000000000000000000000000000000000000000000000000000000000002620000000000000000000000000000000000000000000000000000000000000263000000000000000000000000000000000000000000000000000000000000026400000000000000000000000000000000000000000000000000000000000002650000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000026700000000000000000000000000000000000000000000000000000000000002680000000000000000000000000000000000000000000000000000000000000269000000000000000000000000000000000000000000000000000000000000026a000000000000000000000000000000000000000000000000000000000000026b000000000000000000000000000000000000000000000000000000000000026c000000000000000000000000000000000000000000000000000000000000026d000000000000000000000000000000000000000000000000000000000000026e000000000000000000000000000000000000000000000000000000000000026f00000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000027200000000000000000000000000000000000000000000000000000000000002730000000000000000000000000000000000000000000000000000000000000274000000000000000000000000000000000000000000000000000000000000027500000000000000000000000000000000000000000000000000000000000002760200000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000341100000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000054a0000000000000000000000000000000000000000000000000000000000000541000000000000000000000000000000000000000000000000000000000000054b0000000000000000000000000000000000000000000000000000000000000542000000000000000000000000000000000000000000000000000000000000054c0000000000000000000000000000000000000000000000000000000000000543000000000000000000000000000000000000000000000000000000000000054d0000000000000000000000000000000000000000000000000000000000000544000000000000000000000000000000000000000000000000000000000000054e0000000000000000000000000000000000000000000000000000000000000545000000000000000000000000000000000000000000000000000000000000054f00000000000000000000000000000000000000000000000000000000000005460000000000000000000000000000000000000000000000000000000000000550000000000000000000000000000000000000000000000000000000000000054700000000000000000000000000000000000000000000000000000000000005510000000000000000000000000000000000000000000000000000000000000548000000000000000000000000000000000000000000000000000000000000055200000000000000000000000000000000000000000000000000000000000005490000000000000000000000000000000000000000000000000000000000000553000000000000000000000000000000000000000000000000000000000000054a0000000000000000000000000000000000000000000000000000000000000554000000000000000000000000000000000000000000000000000000000000054b0000000000000000000000000000000000000000000000000000000000000555000000000000000000000000000000000000000000000000000000000000054c0000000000000000000000000000000000000000000000000000000000000556000000000000000000000000000000000000000000000000000000000000054d0000000000000000000000000000000000000000000000000000000000000557000000000000000000000000000000000000000000000000000000000000054e0000000000000000000000000000000000000000000000000000000000000558000000000000000000000000000000000000000000000000000000000000054f0000000000000000000000000000000000000000000000000000000000000559000011000000021c000000b02d622c8b62e54bf51a1fd35b67456b229dae3bc6126977f1b2d88662a3418347a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb61b97deda6e3f6e57e02e3e50f41a6c05d9d508147d543e0d9fbbe858c33d4e6a2185f8c0152162e19e296fff1a7e1664c5c8194faf05fc0450c5725c0de96009000000b014b645af1d3b7df259bc545d7f52bc412546986a5f76ff3b331cb8dbddf1c9c2ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb602ebf7fe2895a0551fcabf530c27bd24616d64b8ca61c55720001ad1fded94e508da11e3cf7794deddc5f101328b67834d6075f3fc13834dd109a4d54899a684000000b02c6ead45b8c3501951a91b1618e165bcd512dd57263df715f742e0e908a2103eb03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb61aa45f94c41d727c17b7860ba5b666a01139a9a59128bd31e42642df289ddb612092797a6aff6705d5b2b7b9cc1a10fefd2cbae0c2da7b28952fcce27349ed000000021c000000b013c2c6697319821691459c1830eeb6db5cab39fb734b7e5f77871362435256b9b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb601f878b87e73a4795754070dbdc3b7be98d20649de36447b646a7558634e21dc07e6929e25559903154f38bbe427621d84c517850fe802721573ff5badfa337b000000b02b7b2e000ea1543d893262d0ca7d60570c777ee83a12763a3bad3b6f6e029d35b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb619b0e04f19fb76a04f40cdc65752613a489e4b36a4fd3c5628909d658dfe68581f9efa34c0dd6b2a0d3bff747db60b9934915c71d6aefa4cd99a2768d8aa79f7000000b012cf4723c8f7863ac8cee3d2e28ab175940fdb8c871ffd83bbf16de8a8b2e3b0bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb60104f972d451a89d8edd4ec86f5fb258d036a7daf20ac39fa8d4cfdec8aeaed306f313587b339d274cd8807695c35cb7bc29b91623bc819659de59e2135ac0720000021c000000b02a87aeba647f5861c0bbaa8b7c195af143dc20794de6f55e801795f5d3632a2cc03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb618bd61096fd97ac486ca158108ee5bd48002ecc7b8d1bb7a6cfaf7ebf35ef54f1eab7aef16bb6f4e44c5472f2f5206336bf5fe02ea8379711e0481ef3e0b06ee000000b011dbc7de1ed58a5f00582b8d9426ac0fcb747d1d9af47ca8005bc86f0e1370a7c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb600117a2d2a2facc1c666968320fbacf3079b496c05df42c3ed3f2a652e0f3bca05ff9412d111a14b8461c831475f5751f38e5aa7379100ba9e48b46878bb4d69000000b029942f74ba5d5c85f844f2462db5558b7b40c20a61bb7482c481f07c38c3b723c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb617c9e1c3c5b77ee8be535d3bba8a566eb7678e58cca63a9eb165527258bf82461db7fba96c9973727c4e8ee9e0ee00cda35a9f93fe57f895626edc75a36b93e50000021c000000b010e8489874b38e8337e1734845c2a6aa02d91eaeaec8fbcc44c622f57373fd9ecc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb62f82495a613f510fb64023f45418ffea6733d345936d3279758b7a7f836fc8c2050c14cd26efa56fbbeb0febf8fb51ec2af2fc384b657fdee2b30eeede1bda60000000b028a0b02f103b60aa2fce3a00df515025b2a5639b758ff3a708ec4b029e24441ad03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb616d6627e1b95830cf5dca4f66c265108eecc2fe9e07ab9c2f5cfacf8be200f3d1cc47c63c2777796b3d7d6a49289fb67dabf4125122c77b9a6d936fc08cc20dc000000b00ff4c952ca9192a76f6abb02f75ea1443a3dc03fc29d7af089307d7bd8d48a95d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb62e8eca14b71d5533edc96baf05b4fa849e9874d6a741b19db9f5d505e8d055b9041895877ccda993f37457a6aa974c8662579dc95f39ff03271d6975437c67570000021c000000b027ad30e9661964ce675781bb90ed4abfea0a052c896472cb4d56a5890384d111d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb615e2e338717387312d65ecb11dc24ba32630d17af44f38e73a3a077f23809c341bd0fd1e18557bbaeb611e5f4425f6021223e2b62600f6ddeb4391826e2cadd3000000b00f014a0d206f96cba6f402bda8fa9bde71a261d0d671fa14cd9ad8023e35178cdc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb62d9b4acf0cfb59582552b369b750f51ed5fd1667bb1630c1fe602f8c4e30e2b003251641d2abadb82afd9f615c33472099bc3f5a730e7e276b87c3fba8dcf44e000000b026b9b1a3bbf768f29ee0c9764289455a216ea6bd9d38f1ef91c1000f68e55e08e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb614ef63f2c7518b5564ef346bcf5e463d5d95730c0823b80b7ea4620588e1292b1add7dd86e337fdf22ea6619f5c1f09c4988844739d576022fadec08d38d3aca0000021c000000b00e0dcac7764d9aefde7d4a785a969678a9070361ea46793912053288a395a483e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb62ca7cb8962d95d7c5cdbfb2468ecefb90d61b7f8ceeaafe642ca8a12b3916fa7023196fc2889b1dc6286e71c0dcf41bad120e0eb86e2fd4baff21e820e3d8145000000b025c6325e11d56d16d66a1130f4253ff458d3484eb10d7113d62b5a95ce45eaffe83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb613fbe4ad1d2f8f799c787c2680fa40d794fa149d1bf8372fc30ebc8bee41b62219e9fe92c41184035a73add4a75deb3680ed25d84da9f5267418468f38edc7c1000000b00d1a4b81cc2b9f14160692330c329112e06ba4f2fe1af85d566f8d0f08f6317aec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb62bb44c43b8b761a0946542df1a88ea5344c65989e2bf2f0a8734e49918f1fc9e013e17b67e67b6009a102ed6bf6b3c550885827c9ab77c6ff45c7908739e0e3c0000021c000000b024d2b31867b3713b0df358eba5c13a8e9037e9dfc4e1f0381a95b51c33a677f6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb613086567730d939dd401c3e132963b71cc5eb62e2fccb6540779171253a2431918f67f4d19ef882791fcf58f58f9e5d0b851c769617e744ab882a1159e4e54b8000000b00c26cc3c2209a3384d8fd9edbdce8bad17d0468411ef77819ad9e7956e56be71f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb62ac0ccfe0e9565c4cbee8a99cc24e4ed7c2afb1af693ae2ecb9f3f1f7e528995004a9870d445ba24d1997691710736ef3fea240dae8bfb9438c6d38ed8fe9b33000000b023df33d2bd91755f457ca0a6575d3528c79c8b70d8b66f5c5f000fa2990704edf83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb61214e621c8eb97c20b8b0b9be432360c03c357bf43a135784be37198b902d010180300076fcd8c4bc9863d4a0a95e06aefb668fa7552f36efcecfb9c03aee1af0000021c000000b00b334cf677e7a75c851921a86f6a86474f34e81525c3f6a5df44421bd3b74b68fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb629cd4db8647369e90377d2547dc0df87b38f9cac0a682d53100999a5e3b3168c2fbb679e0b555e72c1730402a42489e69f82ade73c19eb49c11323a92e5f282b000000b023533ea2e37804772c2110f02930ea449560b0aba9928ecbd3a6cb5f73909f15003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6112166dc1ec99be64314535695ce30a63b27f9505775b49c904dcc1f1e635d07170f80c1c5ab9070010f8504bc31db05271b0a8b8927729341575622690f6ea6000000b00aa757c69dce36746bbd91f2413e3b631cf90d4ff6a0161553eafdd8ae40e590043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6294158888a59f900ea1c429e4f9494a38153c1e6db444cc284b05562be3cb0b42f2f726e313bed8aa817744c75f83f026d46d3220cf60ab935b9df6608e8c25300000fa400000168000000b0225fbf5d3956089b63aa58aadacce4deccc5523cbd670df0181125e5d8f12c0c083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6109571ac44b02afe29b8c3a067a1e5c208ec1e8b2851d40c04f487dbf8ecf72f16838b91eb921f87e7b3f54e8e059020f4df2fc65a039202b5fe11df439908ce000000b009b3d880f3ac3a98a346d9acf2da35fd545daee10a7495399855585f13a172870c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb6284dd942e037fd2521a58a5901308f3db8b86377ef18cbe6c91aafe9239d3dab2e3bf3288719f1aedfa0bc072794399ca4ab74b320ca89dd7a2439ec6e494f4a00000168000000b0216c40178f340cbf9b33a0658c68df790429f3cdd13b8d145c7b806c3e51b903103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb60fa1f2669a8e2f2261420b5b193de05c4050c01c3c265330495ee2625e4d842615900c4c417023ac1f3d3d093fa18abb2c43d1576dd81126fa686c65a8f995c5000000b008c0593b498a3ebcdad02167a47630978bc250721e49145ddcbfb2e57901ff7e143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6275a59fd36160149592ed213b2cc89d7f01d050902ed4b0b0d850a6f88fdcaa22d4873e2dcf7f5d3172a03c1d9303436dc101644349f0901be8e9472d3a9dc4100000168000000b02078c0d1e51210e3d2bce8203e04da133b8e955ee5100c38a0e5daf2a3b245fa183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb60eae7320f06c334698cb5315cad9daf677b561ad4ffad2548dc93ce8c3ae111d149c8d06974e27d056c684c3f13d855563a872e881ac904b3ed2c6ec0e5a22bc000000b007ccd9f59f6842e11259692256122b31c326f203321d9382212a0d6bde628c751c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb62666dab78bf4056d90b819ce646884722781a69a16c1ca2f51ef64f5ee5e57992c54f49d32d5f9f74eb34b7c8acc2ed11374b7d54873882602f8eef9390a693800000168000000b01f85418c3af015080a462fdaefa0d4ad72f336eff8e48b5ce55035790912d2f1203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb60dbaf3db464a376ad0549ad07c75d590af1a033e63cf5178d233976f290e9e1413a90dc0ed2c2bf48e4fcc7ea2d97fef9b0d147995810f6f833d217273baafb3000000b006d95aaff546470549e2b0dd07ae25cbfa8b939445f212a6659467f243c3196c243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb625735b71e1d20991c841618916047f0c5ee6482b2a9649539659bf7c53bee4902b61755788b3fe1b863c93373c68296b4ad959665c48074a4763497f9e6af62f00000168000000b01e91c24690ce192c41cf7795a13ccf47aa57d8810cb90a8129ba8fff6e735fe8283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb60cc774959c283b8f07dde28b2e11d02ae67ea4cf77a3d09d169df1f58e6f2b0b12b58e7b430a3018c5d9143954757a89d271b60aa9558e93c7a77bf8d91b3caa000000b005e5db6a4b244b29816bf897b94a206631f0352559c691caa9fec278a923a6632c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb6247fdc2c37b00db5ffcaa943c7a079a6964ae9bc3e6ac877dac41a02b91f71872a6df611de92023fbdc5daf1ee042405823dfaf7701c866e8bcda40603cb832600000168000000b01d9e4300e6ac1d507958bf5052d8c9e1e1bc7a12208d89a56e24ea85d3d3ecdf303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb60bd3f54ff2063fb33f672a45dfadcac51de346608b784fc15b084c7bf3cfb80211c20f3598e8343cfd625bf40611752409d6579bbd2a0db80c11d67f3e7bc9a1000000b004f25c24a1024f4db8f540526ae61b006954d6b66d9b10eeee691cff0e84335a343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6238c5ce68d8e11da3753f0fe793c7440cdaf8b4d523f479c1f2e74891e7ffe7e297a76cc34700663f54f22ac9fa01e9fb9a29c8883f10592d037fe8c692c101d00000168000000b01caac3bb3c8a2174b0e2070b0474c47c19211ba3346208c9b28f450c393479d6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb60ae0760a47e443d776f072009149c55f5547e7f19f4ccee59f72a702593044f910ce8fefeec6386134eba3aeb7ad6fbe413af92cd0fe8cdc507c3105a3dc5698000000b003fedcdef6e05371f07e880d1c82159aa0b97847816f901332d3778573e4c0513c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb62298dda0e36c15fe6edd38b92ad86edb05142cde6613c6c06398cf0f83e08b752886f7868a4e0a882cd86a67513c1939f1073e1997c584b714a25912ce8c9d1400000168000000b01bb7447592682598e86b4ec5b610bf165085bd34483687edf6f99f929e9506cd403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb609ecf6c49dc247fbae79b9bb42e5bff98cac8982b3214e09e3dd0188be90d1f00fdb10aa44a43c856c74eb6969496a58789f9abde4d30c0094e68b8c093ce38f000000b0030b5d994cbe57962807cfc7ce1e1034d81e19d895440f37773dd20bd9454d48443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb621a55e5b394a1a22a6668073dc7469753c78ce6f79e845e4a8032995e941186c27937840e02c0eac6461b22202d813d4286bdfaaab9a03db590cb39933ed2a0b00000168000000b01ac3c52fe84629bd1ff4968067acb9b087ea5ec55c0b07123b63fa1903f593c4483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb608f9777ef3a04c1fe6030175f481ba93c4112b13c6f5cd2e28475c0f23f15ee70ee791649a8240a9a3fe33241ae564f2b0043c4ef8a78b24d950e6126e9d7086000000b00217de53a29c5bba5f9117827fba0acf0f82bb69a9188e5bbba82c923ea5da3f4c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb620b1df158f281e46ddefc82e8e10640f73dd70008dbcc508ec6d841c4ea1a563269ff8fb360a12d09beaf9dcb4740e6e5fd0813bbf6e82ff9d770e1f994db70200000168000000b019d045ea3e242de1577dde3b1948b44abf4f00566fdf86367fce549f695620bb503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb60805f839497e50441d8c4930a61db52dfb75cca4daca4c526cb1b6958951ebde0df4121ef06044cddb877adecc815f8ce768dde00c7c0a491dbb4098d3fdfd7d000000b001245f0df87a5fde971a5f3d3156056946e75cfabced0d8000128718a4066736543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb61fbe5fcfe506226b15790fe93fac5ea9ab421191a191442d30d7dea2b402325a25ac79b58be816f4d374419766100908973522ccd3430223e1e168a5feae43f900000168000000b018dcc6a4940232058f0725f5cae4aee4f6b3a1e783b4055ac438af25ceb6adb2583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6071278f39f5c5468551590eb57b9afc832da6e35ee9ecb76b11c111beeb278d50d0092d9463e48f21310c2997e1d5a271ecd7f712050896d62259b1f395e8a74000000b00030dfc84e586402cea3a6f7e2f200037e4bfe8bd0c18ca4447ce19f0966f42d5c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb61ecae08a3ae4268f4d0257a3f1485943e2a6b322b565c351754239291962bf5124b8fa6fe1c61b190afd895217ac03a2ce99c45de7178148264bc32c640ed0f0380000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018100000000000000000000000000000000000000000000000000000000000001820000000000000000000000000000000000000000000000000000000000000183000000000000000000000000000000000000000000000000000000000000018400000000000000000000000000000000000000000000000000000000000001850000000000000000000000000000000000000000000000000000000000000186000000000000000000000000000000000000000000000000000000000000018700000000000000000000000000000000000000000000000000000000000001880000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000018a000000000000000000000000000000000000000000000000000000000000018b000000000000000000000000000000000000000000000000000000000000018c000000000000000000000000000000000000000000000000000000000000018d000000000000000000000000000000000000000000000000000000000000018e000000000000000000000000000000000000000000000000000000000000018f0000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000019100000000000000000000000000000000000000000000000000000000000001920000000000000000000000000000000000000000000000000000000000000193000000000000000000000000000000000000000000000000000000000000019400000000000000000000000000000000000000000000000000000000000001950000000000000000000000000000000000000000000000000000000000000196000000000000000000000000000000000000000000000000000000000000019700000000000000000000000000000000000000000000000000000000000001980000000000000000000000000000000000000000000000000000000000000199000000000000000000000000000000000000000000000000000000000000019a000000000000000000000000000000000000000000000000000000000000019b000000000000000000000000000000000000000000000000000000000000019c000000000000000000000000000000000000000000000000000000000000019d000000000000000000000000000000000000000000000000000000000000019e000000000000000000000000000000000000000000000000000000000000019f00000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001a100000000000000000000000000000000000000000000000000000000000001a200000000000000000000000000000000000000000000000000000000000001a300000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000000001a500000000000000000000000000000000000000000000000000000000000001a600000000000000000000000000000000000000000000000000000000000001a700000000000000000000000000000000000000000000000000000000000001a800000000000000000000000000000000000000000000000000000000000001a900000000000000000000000000000000000000000000000000000000000001aa00000000000000000000000000000000000000000000000000000000000001ab00000000000000000000000000000000000000000000000000000000000001ac00000000000000000000000000000000000000000000000000000000000001ad00000000000000000000000000000000000000000000000000000000000001ae00000000000000000000000000000000000000000000000000000000000001af00000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b100000000000000000000000000000000000000000000000000000000000001b200000000000000000000000000000000000000000000000000000000000001b300000000000000000000000000000000000000000000000000000000000001b400000000000000000000000000000000000000000000000000000000000001b500000000000000000000000000000000000000000000000000000000000001b600000000000000000000000000000000000000000000000000000000000001b7370000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000028100000000000000000000000000000000000000000000000000000000000002820000000000000000000000000000000000000000000000000000000000000283000000000000000000000000000000000000000000000000000000000000028400000000000000000000000000000000000000000000000000000000000002850000000000000000000000000000000000000000000000000000000000000286000000000000000000000000000000000000000000000000000000000000028700000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000289000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000028b000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028d000000000000000000000000000000000000000000000000000000000000028e000000000000000000000000000000000000000000000000000000000000028f0000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000029100000000000000000000000000000000000000000000000000000000000002920000000000000000000000000000000000000000000000000000000000000293000000000000000000000000000000000000000000000000000000000000029400000000000000000000000000000000000000000000000000000000000002950000000000000000000000000000000000000000000000000000000000000296000000000000000000000000000000000000000000000000000000000000029700000000000000000000000000000000000000000000000000000000000002980000000000000000000000000000000000000000000000000000000000000299000000000000000000000000000000000000000000000000000000000000029a000000000000000000000000000000000000000000000000000000000000029b000000000000000000000000000000000000000000000000000000000000029c000000000000000000000000000000000000000000000000000000000000029d000000000000000000000000000000000000000000000000000000000000029e000000000000000000000000000000000000000000000000000000000000029f00000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a100000000000000000000000000000000000000000000000000000000000002a200000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000002a400000000000000000000000000000000000000000000000000000000000002a500000000000000000000000000000000000000000000000000000000000002a600000000000000000000000000000000000000000000000000000000000002a700000000000000000000000000000000000000000000000000000000000002a800000000000000000000000000000000000000000000000000000000000002a900000000000000000000000000000000000000000000000000000000000002aa00000000000000000000000000000000000000000000000000000000000002ab00000000000000000000000000000000000000000000000000000000000002ac00000000000000000000000000000000000000000000000000000000000002ad00000000000000000000000000000000000000000000000000000000000002ae00000000000000000000000000000000000000000000000000000000000002af00000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002b100000000000000000000000000000000000000000000000000000000000002b200000000000000000000000000000000000000000000000000000000000002b300000000000000000000000000000000000000000000000000000000000002b400000000000000000000000000000000000000000000000000000000000002b500000000000000000000000000000000000000000000000000000000000002b60200000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000381100000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000058a0000000000000000000000000000000000000000000000000000000000000581000000000000000000000000000000000000000000000000000000000000058b0000000000000000000000000000000000000000000000000000000000000582000000000000000000000000000000000000000000000000000000000000058c0000000000000000000000000000000000000000000000000000000000000583000000000000000000000000000000000000000000000000000000000000058d0000000000000000000000000000000000000000000000000000000000000584000000000000000000000000000000000000000000000000000000000000058e0000000000000000000000000000000000000000000000000000000000000585000000000000000000000000000000000000000000000000000000000000058f00000000000000000000000000000000000000000000000000000000000005860000000000000000000000000000000000000000000000000000000000000590000000000000000000000000000000000000000000000000000000000000058700000000000000000000000000000000000000000000000000000000000005910000000000000000000000000000000000000000000000000000000000000588000000000000000000000000000000000000000000000000000000000000059200000000000000000000000000000000000000000000000000000000000005890000000000000000000000000000000000000000000000000000000000000593000000000000000000000000000000000000000000000000000000000000058a0000000000000000000000000000000000000000000000000000000000000594000000000000000000000000000000000000000000000000000000000000058b0000000000000000000000000000000000000000000000000000000000000595000000000000000000000000000000000000000000000000000000000000058c0000000000000000000000000000000000000000000000000000000000000596000000000000000000000000000000000000000000000000000000000000058d0000000000000000000000000000000000000000000000000000000000000597000000000000000000000000000000000000000000000000000000000000058e0000000000000000000000000000000000000000000000000000000000000598000000000000000000000000000000000000000000000000000000000000058f0000000000000000000000000000000000000000000000000000000000000599000011000000021c000000b017e9475ee9e03629c6906db07c80a97f2e1843789788847f08a309ac34173aa9603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6061ef9adf53a588c8c9ed8a60955aa626a3f0fc702734a9af5866ba2541305cc0c0d13939c1c4d164a9a0a542fb954c15632210234250891a68ff5a59ebf176b000000b02fa1aef585680850be7d3469160f52fadde488655e4f7c59ccc931b95ec78125643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb61dd7614490c22ab3848b9f5ea2e453de1a0b54b3c93a4275b9ac93af7ec34c4823c57b2a37a41f3d4286d10cc947fe3d05fe65eefaec006c6ab61db2c96f5de7000000b016f5c8193fbe3a4dfe19b56b2e1ca419657ce509ab5d03a34d0d64329977c7a0683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6052b7a684b185cb0c4282060baf1a4fca1a3b1581647c9bf39f0c628b97392c30b19944df1fa513a8223520ee1554f5b8d96c29347f987b5eafa502c041fa4620000021c000000b02eae2fafdb460c74f6067c23c7ab4d95154929f67223fb7e11338c3fc4280e1c6c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb61ce3e1fee6a02ed7bc14e71954804e78516ff644dd0ec199fe16ee35e423d93f22d1fbe48d8223617a1018c77ae3f8d73d6307800ec07f90af2078392ecfeade000000b0160248d3959c3e7235a2fd25dfb89eb39ce1869abf3182c79177beb8fed85497703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb60437fb22a0f660d4fbb1681b6c8d9f96d90852e92a1c48e37e5b20af1ed41fba0a26150847d8555eb9ac99c992f149f5c4fb64245bce06da2f64aab269803159000000b02dbab06a312410992d8fc3de7947482f4cadcb8785f87aa2559de6c629889b13743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb61bf062b93c7e32fbf39e2ed4061c491288d497d5f0e340be428148bc4984663621de7c9ee3602785b19960822c7ff37174c7a9112294feb4f38ad2bf943077d50000021c000000b0150ec98deb7a42966d2c44e09154994dd446282bd30601ebd5e2193f6438e18e783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb603447bdcf6d464f9333aafd61e299a31106cf47a3df0c807c2c57b358434acb1093295c29db65982f135e184448d448ffc6005b56fa285fe73cf0538cee0be50000000b02cc73124870214bd65190b992ae342c984126d1899ccf9c69a08414c8ee9280a7c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb61afce373925c37202b27768eb7b843acc039396704b7bfe286eba342aee4f32d20eafd59393e2ba9e922a83cde1bee0bac2c4aa236697dd937f52d45f99104cc000000b0141b4a48415846baa4b58c9b42f093e80baac9bce6da81101a4c73c5c9996e85803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb60250fc974cb2691d6ac3f790cfc594cb47d1960b51c5472c072fd5bbe99539a8083f167cf3945da728bf293ef6293f2a33c4a74683770522b8395fbf34414b470000021c000000b02bd3b1dedce018e19ca25353dc7f3d63bb770ea9ada178eade729bd2f449b501843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb61a09642de83a3b4462b0be4969543e46f79ddaf8188c3f06cb55fdc9144580241ff77e138f1c2fce20abeff78fb7e8a5e390ec334a3dfcfd7c5f87cc5ef191c3000000b01327cb0297364adedc3ed455f48c8e82430f6b4dfaaf00345eb6ce4c2ef9fb7c883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6015d7d51a2906d41a24d3f4b81618f657f36379c6599c6504b9a30424ef5c69f074b9737497261cb604870f9a7c539c46b2948d7974b8446fca3ba4599a1d83e000000b02ae0329932be1d05d42b9b0e8e1b37fdf2dbb03ac175f80f22dcf65959aa41f88c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb61915e4e83e183f689a3a06041af038e12f027c892c60be2b0fc0584f79a60d1b1f03fecde4fa33f2583537b24153e3401af58dc45e127c21c0c9e252c4521eba0000021c000000b012344bbced144f0313c81c10a628891c7a740cdf0e837f58a32128d2945a8873903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb60069fe0bf86e7165d9d6870632fd89ffb69ad92d796e457490048ac8b4565396065817f19f5065ef97d1b8b45961345ea28dea68ab20036b410e14cbff026535000000b029ecb353889c212a0bb4e2c93fb732982a4051cbd54a7733674750dfbf0aceef943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6182265a293f6438cd1c34dbecc8c337b66671e1a40353d4f542ab2d5df069a121e107f883ad838168fbe7f6cf2efddda525a2f5571e6fb4605343cd929b2abb1000000b01140cc7742f253274b5163cb57c483b6b1d8ae702257fe7ce78b8358f9bb156a983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb62fdacd392f7e15b3c9b01477661adcf71633630706fc352a1850dae309b6e08e056498abf52e6a13cf5b006f0afd2ef8d9f28bf9bef4828f85786f526462f22c0000021c000000b028f9340dde7a254e433e2a83f1532d3261a4f35ce91ef657abb1ab66246b5be69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb6172ee65ce9d447b1094c95797e282e159dcbbfab5409bc7398950d5c446727091d1d004290b63c3ac747c727a48bd87489bed0e685bb7a6a499e975f8f1338a8000000b0104d4d3198d0574b82daab8609607e50e93d5001362c7da12bf5dddf5f1ba261a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb62ee74df3855c19d801395c3217b6d7914d9804981ad0b44e5cbb35696f176d85047119664b0c6e3806e44829bc99299311572d8ad2c901b3c9e2c9d8c9c37f23000000b02805b4c8345829727ac7723ea2ef27cc990994edfcf3757bf01c05ec89cbe8dda43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6163b67173fb24bd540d5dd342fc428afd530613c67de3b97dcff67e2a9c7b4001c2980fce694405efed10ee25627d30ec1237277998ff98e8e08f1e5f473c59f0000021c000000b00f59cdebeeae5b6fba63f340bafc78eb20a1f1924a00fcc570603865c47c2f58a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb62df3ceaddb3a1dfc38c2a3ecc952d22b84fca6292ea53372a1258fefd477fa7c037d9a20a0ea725c3e6d8fe46e35242d48bbcf1be69d80d80e4d245f2f240c1a000000b0271235828a362d96b250b9f9548b2266d06e367f10c7f4a034866072ef2c75d4ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb61547e7d195904ff9785f24eee160234a0c9502cd7bb2babc2169c2690f2840f71b3601b73c724483365a569d07c3cda8f8881408ad6478b2d2734c6c59d45296000000b00e664ea6448c5f93f1ed3afb6c987385580693235dd57be9b4ca92ec29dcbc4fb03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb62d004f6831182220704beba77aeeccc5bc6147ba4279b296e58fea7639d88773028a1adaf6c8768075f6d79f1fd11ec7802070acfa71fffc52b77ee5948499110000021c000000b0261eb63ce01431bae9da01b406271d0107d2d810249c73c478f0baf9548d02cbb43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb61454688beb6e541dafe86ca992fc1de443f9a45e8f8739e065d41cef7488cdee1a428271925048a76de39e57b95fc8432fecb599c138f7d716dda6f2bf34df8d000000b00d72cf609a6a63b8297682b61e346e1f8f6b34b471a9fb0df934ed728f3d4946b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb62c0cd02286f62644a7d533622c8ac75ff3c5e94b564e31bb29fa44fc9f39146a01969b954ca67aa4ad801f59d16d1961b785123e0e467f209721d96bf9e52608000000b0252b36f735f235df2163496eb7c3179b3f3779a13870f2e8bd5b157fb9ed8fc2bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb61360e946414c5841e771b4644498187e7b5e45efa35bb904aa3e7775d9e95ae5194f032be82e4ccba56ce6126afbc2dd6751572ad50d76fb5b48017924956c8400000fa400000168000000b00c7f501af04867dc60ffca70cfd068b9c6cfd645857e7a323d9f47f8f49dd63dc03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb62b1950dcdcd42a68df5e7b1cde26c1fa2b2a8adc6a22b0df6e649f830499a16100a31c4fa2847ec8e5096714830913fbeee9b3cf221afe44db8c33f25f45b2ff000000b02437b7b18bd03a0358ec9129695f1235769c1b324c45720d01c570061f4e1cb9c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6126d6a00972a5c661efafc1ef6341318b2c2e780b7303828eea8d1fc3f49e7dc185b83e63e0c50efdcf62dcd1c97bd779eb5f8bbe8e1f61f9fb25bff89f5f97b00000168000000b00b8bd0d546266c009889122b816c6353fe3477d69952f9568209a27f59fe6334c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb62a25d19732b22e8d16e7c2d78fc2bc94628f2c6d7df73003b2cefa0969fa2e583013eb7cd9942316d4e2f485b62666f34e823da8afa8edfa63d8840cb4a63ff7000000b02344386be1ae3e279075d8e41afb0ccfae00bcc36019f131462fca8c84aea9b0cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb61179eabaed08608a568443d9a7d00db2ea278911cb04b74d33132c82a4aa74d3176804a093ea5514147f7587ce33b811d61a9a4cfcb67543e41cb685ef56867200000168000000b00a98518f9c047024d01259e633085dee35991967ad27787ac673fd05bf5ef02bd03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb629325251889032b14e710a92415eb72e99f3cdfe91cbaf27f739548fcf5abb4f2f206c372f72273b0c6c3c4067c2618d85e6df39c37d6d1ea842de931a06ccee000000b02250b926378c424bc7ff209ecc970769e5655e5473ee70558a9a2512ea0f36a7d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb610866b7542e664ae8e0d8b94596c084d218c2aa2ded93671777d87090a0b01ca1674855ae9c859384c08bd427fcfb2ac0d7f3bde108af4682887110c54b7136900000168000000b009a4d249f1e27449079ba1a0e4a458886cfdbaf8c0fbf79f0ade578c24bf7d22d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6283ed30bde6e36d585fa524cf2fab1c8d1586f8fa5a02e4c3ba3af1634bb48462e2cecf185502b5f43f583fb195e5c27bd4b80cad751ec42ecad39197f6759e5000000b0215d39e08d6a466fff8868597e3302041cc9ffe587c2ef79cf047f994f6fc39edc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb60f92ec2f98c468d2c596d34f0b0802e758f0cc33f2adb595bbe7e18f6f6b8ec1158106153fa65d5c839204fd316bad4644e3dd6f245f738c6cf16b92ba17a06000000168000000b008b1530447c0786d3f24e95b96405322a4625c89d4d076c34f48b2128a200a19e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6274b53c6344c3af9bd839a07a496ac6308bd1120b974ad70800e099c9a1bd53d2d396dabdb2e2f837b7ecbb5cafa56c1f4b0225beb266b673117939fe4c7e6dc000000b02069ba9ae3484a943711b0142fcefc9e542ea1769b976e9e136eda1fb4d05095e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb60e9f6ce9eea26cf6fd201b09bca3fd8190556dc5068234ba00523c15d4cc1bb8148d86cf95846180bb1b4cb7e307a7e07c487f003833f2b0b15bc6191f782d5700000168000000b007bdd3be9d9e7c9176ae311647dc4dbcdbc6fe1ae8a4f5e793b30c98ef809710e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb62657d4808a2a3f1df50ce1c25632a6fd4021b2b1cd492c94c4786422ff7c62342c45ee66310c33a7b30813707c96515c2c14c3ecfefaea8b7581ee264a2873d3000000b01f763b5539264eb86e9af7cee16af7388b934307af6bedc257d934a61a30dd8cec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb60dabeda44480711b34a962c46e3ff81bc7ba0f561a56b3de44bc969c3a2ca8af139a0789eb6265a4f2a4947294a3a27ab3ad20914c0871d4f5c6209f84d8ba4e00000168000000b006ca5478f37c80b5ae3778d0f9784857132b9fabfc79750bd81d671f54e12407f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb62564553ae00843422c96297d07cea19777865442e11dabb908e2bea964dcef2b2b526f2086ea37cbea915b2b2e324bf66379657e12cf69afb9ec48acaf8900ca000000b01e82bc0f8f0452dca6243f899306f1d2c2f7e498c3406ce69c438f2c7f916a83f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb60cb86e5e9a5e753f6c32aa7f1fdbf2b5ff1eb0e72e2b33028926f1229f8d35a612a68844414069c92a2ddc2d463f9d14eb11c2225fdcf0f93a307b25ea39474500000168000000b005d6d533495a84d9e5c0c08bab1442f14a90413d104df4301c87c1a5ba41b0fef83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb62470d5f535e64766641f7137b96a9c31aeeaf5d3f4f22add4d4d192fca3d7c222a5eefdadcc83bf0221aa2e5dfce46909ade070f26a3e8d3fe56a33314e98dc1000000b01d8f3cc9e4e25700ddad874444a2ec6cfa5c8629d714ec0ae0ade9b2e4f1f77afc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb60bc4ef18f03c7963a3bbf239d177ed503683527841ffb226cd914ba904edc29d11b308fe971e6ded61b723e7f7db97af227663b373b1701d7e9ad5ac4f99d43c00000168000000b0054ae0036f4113f1cc6530d57ce7f80d18546677e12a139f912e7d6294cb4b2600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb6237d56af8bc44b8a9ba8b8f26b0696cbe64f976508c6aa0191b773b62f9e0919296b709532a6401459a3eaa0916a412ad242a8a03a7867f842c0fdb97a4a1ab8000000b01d03479a0ac8e618c451f78e1676a188c820ab64a7f10b7a5554a56fbf7b91a204400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb60b38f9e91623087b8a606283a34ba26c044777b312dbd19642380765df775cc5112713cebd04fd05485b9431c9af4ccaf03a88ee448d8f8cf34191692a236e6400000168000000b0045760bdc51f181603ee78902e83f2a74fb90808f4fe92c3d598d7e8fa2bd81d08400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb622f1617fb1aadaa2824d293c3cda4be7b413bc9fd9a2c971065e2f730a27a34128df7b65588ccf2c40485aea633df646a006cddb0b548767b767b97654d3b4e0000000b01c0fc85460a6ea3cfbdb3f48c8129c22ff854cf5bbc58a9e99befff624dc1e990c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60a457aa36c010c9fc1e9aa3e54e79d063bac194426b050ba86a261ec44d7e9bc1033948912e301297fe4dbec7b4b4765279f2a7f58620eb137abebef8f83fb5b00000168000000b00363e1781afd1c3a3b77c04ae01fed41871da99a08d311e81a03326f5f8c651410400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb621fde23a0788dec6b9d670f6ee764681eb785e30ed7748954ac889f96f88303827ebfc1fae6ad35077d1a2a514d9f0e0d76b6f6c1f29068bfbd213fcba3441d7000000b01b1c490eb684ee613364870379ae96bd36e9ee86cf9a09c2de295a7c8a3cab9014400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb60951fb5dc1df10c3f972f1f9068397a07310bad53a84cfdecb0cbc72aa3876b30f40154368c1054db76e23a72ce741ff5f03cc106c368dd57c164675f4e488523800000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000001c100000000000000000000000000000000000000000000000000000000000001c200000000000000000000000000000000000000000000000000000000000001c300000000000000000000000000000000000000000000000000000000000001c400000000000000000000000000000000000000000000000000000000000001c500000000000000000000000000000000000000000000000000000000000001c600000000000000000000000000000000000000000000000000000000000001c700000000000000000000000000000000000000000000000000000000000001c800000000000000000000000000000000000000000000000000000000000001c900000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000001cb00000000000000000000000000000000000000000000000000000000000001cc00000000000000000000000000000000000000000000000000000000000001cd00000000000000000000000000000000000000000000000000000000000001ce00000000000000000000000000000000000000000000000000000000000001cf00000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001d100000000000000000000000000000000000000000000000000000000000001d200000000000000000000000000000000000000000000000000000000000001d300000000000000000000000000000000000000000000000000000000000001d400000000000000000000000000000000000000000000000000000000000001d500000000000000000000000000000000000000000000000000000000000001d600000000000000000000000000000000000000000000000000000000000001d700000000000000000000000000000000000000000000000000000000000001d800000000000000000000000000000000000000000000000000000000000001d900000000000000000000000000000000000000000000000000000000000001da00000000000000000000000000000000000000000000000000000000000001db00000000000000000000000000000000000000000000000000000000000001dc00000000000000000000000000000000000000000000000000000000000001dd00000000000000000000000000000000000000000000000000000000000001de00000000000000000000000000000000000000000000000000000000000001df00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001e100000000000000000000000000000000000000000000000000000000000001e200000000000000000000000000000000000000000000000000000000000001e300000000000000000000000000000000000000000000000000000000000001e400000000000000000000000000000000000000000000000000000000000001e500000000000000000000000000000000000000000000000000000000000001e600000000000000000000000000000000000000000000000000000000000001e700000000000000000000000000000000000000000000000000000000000001e800000000000000000000000000000000000000000000000000000000000001e900000000000000000000000000000000000000000000000000000000000001ea00000000000000000000000000000000000000000000000000000000000001eb00000000000000000000000000000000000000000000000000000000000001ec00000000000000000000000000000000000000000000000000000000000001ed00000000000000000000000000000000000000000000000000000000000001ee00000000000000000000000000000000000000000000000000000000000001ef00000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000001f100000000000000000000000000000000000000000000000000000000000001f200000000000000000000000000000000000000000000000000000000000001f300000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000001f600000000000000000000000000000000000000000000000000000000000001f73700000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002c100000000000000000000000000000000000000000000000000000000000002c200000000000000000000000000000000000000000000000000000000000002c300000000000000000000000000000000000000000000000000000000000002c400000000000000000000000000000000000000000000000000000000000002c500000000000000000000000000000000000000000000000000000000000002c600000000000000000000000000000000000000000000000000000000000002c700000000000000000000000000000000000000000000000000000000000002c800000000000000000000000000000000000000000000000000000000000002c900000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000002cb00000000000000000000000000000000000000000000000000000000000002cc00000000000000000000000000000000000000000000000000000000000002cd00000000000000000000000000000000000000000000000000000000000002ce00000000000000000000000000000000000000000000000000000000000002cf00000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002d100000000000000000000000000000000000000000000000000000000000002d200000000000000000000000000000000000000000000000000000000000002d300000000000000000000000000000000000000000000000000000000000002d400000000000000000000000000000000000000000000000000000000000002d500000000000000000000000000000000000000000000000000000000000002d600000000000000000000000000000000000000000000000000000000000002d700000000000000000000000000000000000000000000000000000000000002d800000000000000000000000000000000000000000000000000000000000002d900000000000000000000000000000000000000000000000000000000000002da00000000000000000000000000000000000000000000000000000000000002db00000000000000000000000000000000000000000000000000000000000002dc00000000000000000000000000000000000000000000000000000000000002dd00000000000000000000000000000000000000000000000000000000000002de00000000000000000000000000000000000000000000000000000000000002df00000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002e100000000000000000000000000000000000000000000000000000000000002e200000000000000000000000000000000000000000000000000000000000002e300000000000000000000000000000000000000000000000000000000000002e400000000000000000000000000000000000000000000000000000000000002e500000000000000000000000000000000000000000000000000000000000002e600000000000000000000000000000000000000000000000000000000000002e700000000000000000000000000000000000000000000000000000000000002e800000000000000000000000000000000000000000000000000000000000002e900000000000000000000000000000000000000000000000000000000000002ea00000000000000000000000000000000000000000000000000000000000002eb00000000000000000000000000000000000000000000000000000000000002ec00000000000000000000000000000000000000000000000000000000000002ed00000000000000000000000000000000000000000000000000000000000002ee00000000000000000000000000000000000000000000000000000000000002ef00000000000000000000000000000000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000002f100000000000000000000000000000000000000000000000000000000000002f200000000000000000000000000000000000000000000000000000000000002f300000000000000000000000000000000000000000000000000000000000002f400000000000000000000000000000000000000000000000000000000000002f500000000000000000000000000000000000000000000000000000000000002f60200000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c11000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000005ca00000000000000000000000000000000000000000000000000000000000005c100000000000000000000000000000000000000000000000000000000000005cb00000000000000000000000000000000000000000000000000000000000005c200000000000000000000000000000000000000000000000000000000000005cc00000000000000000000000000000000000000000000000000000000000005c300000000000000000000000000000000000000000000000000000000000005cd00000000000000000000000000000000000000000000000000000000000005c400000000000000000000000000000000000000000000000000000000000005ce00000000000000000000000000000000000000000000000000000000000005c500000000000000000000000000000000000000000000000000000000000005cf00000000000000000000000000000000000000000000000000000000000005c600000000000000000000000000000000000000000000000000000000000005d000000000000000000000000000000000000000000000000000000000000005c700000000000000000000000000000000000000000000000000000000000005d100000000000000000000000000000000000000000000000000000000000005c800000000000000000000000000000000000000000000000000000000000005d200000000000000000000000000000000000000000000000000000000000005c900000000000000000000000000000000000000000000000000000000000005d300000000000000000000000000000000000000000000000000000000000005ca00000000000000000000000000000000000000000000000000000000000005d400000000000000000000000000000000000000000000000000000000000005cb00000000000000000000000000000000000000000000000000000000000005d500000000000000000000000000000000000000000000000000000000000005cc00000000000000000000000000000000000000000000000000000000000005d600000000000000000000000000000000000000000000000000000000000005cd00000000000000000000000000000000000000000000000000000000000005d700000000000000000000000000000000000000000000000000000000000005ce00000000000000000000000000000000000000000000000000000000000005d800000000000000000000000000000000000000000000000000000000000005cf00000000000000000000000000000000000000000000000000000000000005d9000011000000021c000000b00270623270db205e7301080591bbe7dbbe824b2b1ca7910c5e6d8cf5c4ecf20b18400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb6210a62f45d66e2eaf15fb8b1a012411c22dcffc2014bc7b98f32e47fd4e8bd2f26f87cda0448d774af5aea5fc675eb7b0ed010fd32fd85b0403c6e831f94cece000000b01a28c9c90c62f2856aedcebe2b4a91576e4e9017e36e88e72293b502ef9d38871c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb6085e7c1817bd14e830fc39b3b81f923aaa755c664e594f030f7716f90f9903aa0e4c95fdbe9f0971eef76b61de833c9996686da1800b0cf9c080a0fc5a451549000000b0017ce2ecc6b92482aa8a4fc04357e275f5e6ecbc307c1030a2d7e77c2a4d7f0220400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb62016e3aeb344e70f28e9006c51ae3bb65a41a153152046ddd39d3f063a494a262604fd945a26db98e6e4321a7811e6154634b28e46d204d484a6c90984f55bc50000021c000000b019354a836240f6a9a2771678dce68bf1a5b331a8f743080b66fe0f8954fdc57e24400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb6076afcd26d9b190c6885816e69bb8cd4e1d9fdf7622dce2753e1717f74f990a10d5916b8147d0d962680b31c901f3733cdcd0f3293df8c1e04eafb82bfa5a240000000b0008963a71c9728a6e213977af4f3dd102d4b8e4d44508f54e74242028fae0bf928400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb61f2364690922eb3360724827034a365091a642e428f4c6021807998c9fa9d71d25117e4eb004dfbd1e6d79d529ade0af7d99541f5aa683f8c911238fea55e8bc000000b01841cb3db81efacdda005e338e82868bdd17d33a0b17872fab686a0fba5e52752c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb606777d8cc3791d30a00ec9291b57876f193e9f8876024d4b984bcc05da5a1d980c6597726a5b11ba5e09fad741bb31ce0531b0c3a7b40b424955560925062f370000021c000000b02ffa32d453a6ccf4d1ed24ec281130078ce41826d1de7f0a6f8e921ce50e98f130400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb61e2fe5235f00ef5797fb8fe1b4e630eac90ae4753cc945265c71f413050a6414241dff0905e2e3e155f6c18fdb49db49b4fdf5b06e7b031d0d7b7e164fb675b3000000b0174e4bf80dfcfef21189a5ee401e8126147c74cb1eec0653efd2c4961fbedf6c34400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb60583fe4719572154d79810e3ccf3820950a3411989d6cc6fdcb6268c3fbaaa8f0b72182cc03915de95934291f3572c683c965254bb888a668dbfb08f8a66bc2e000000b02f06b38ea984d11909766ca6d9ad2aa1c448b9b7e5b2fe2eb3f8eca34a6f25e838400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb61d3c65ddb4def37bcf84d79c66822b85006f8606509dc44aa0dc4e996a6af10b232a7fc35bc0e8058d80094a8ce5d5e3ec629741824f824151e5d89cb51702aa0000021c000000b0165accb263db03164912eda8f1ba7bc04be1165c32c08578343d1f1c851f6c633c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb604907f016f3525790f21589e7e8f7ca38807e2aa9dab4b9421208112a51b37860a7e98e716171a02cd1c8a4ca4f3270273faf3e5cf5d098ad22a0b15efc74925000000b02e133448ff62d53d40ffb4618b49253bfbad5b48f9877d52f8634729afcfb2df40400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb61c48e6980abcf7a0070e1f57181e261f37d427976472436ee546a91fcfcb7e022237007db19eec29c50951053e81d07e23c738d296240165965033231a778fa1000000b015674d6cb9b9073a809c3563a356765a8345b7ed4695049c78a779a2ea7ff95a44400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb6039cffbbc513299d46aaa059302b773dbf6c843bb17fcab8658adb990a7bc47d098b19a16bf51e2704a5d207568f219cab5f9576e33188af1694659c5527d61c0000021c000000b02d1fb5035540d9617888fc1c3ce51fd63311fcda0d5bfc773ccda1b015303fd648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb61b556752609afbc43e976711c9ba20b96f38c9287846c29329b103a6352c0af921438138077cf04dfc9298bff01dcb185b2bda63a9f88089daba8da97fd81c98000000b01473ce270f970b5eb8257d1e54f270f4baaa597e5a6983c0bd11d4294fe086514c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb602a980761af12dc17e33e813e1c771d7f6d125ccc55449dca9f5361f6fdc517408979a5bc1d3224b3c2f19c2082b1c36e2c43707f70607d35afec022ba886313000000b02c2c35bdab1edd85b01243d6ee811a706a769e6b21307b9b8137fc367a90cccd50400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb61a61e80cb678ffe87620aecc7b561b53a69d6ab98c1b41b76e1b5e2c9a8c97f0205001f25d5af472341be07aa1b9c5b292907bf4bdccffae1f24e82fe538a98f0000021c000000b013804ee165750f82efaec4d9068e6b8ef20efb0f6e3e02e5017c2eafb541134854400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb601b6013070cf31e5b5bd2fce93636c722e35c75dd928c900ee5f90a5d53cde6b07a41b1617b1266f73b8617cb9c716d11a28d8990ada86f79f691aa91fe8f00a000000b02b38b67800fce1a9e79b8b91a01d150aa1db3ffc3504fabfc5a256bcdff159c458400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb6196e68c70c57040cada9f6872cf215edde020c4a9fefc0dbb285b8b2ffed24e71f5c82acb338f8966ba528355355c04cc9f51d85d1a17ed2638f42b64a993686000000b0128ccf9bbb5313a727380c93b82a662929739ca08212820945e689361aa1a03f5c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb600c281eac6ad3609ed46778944ff670c659a68eeecfd482532c9eb2c3a9d6b6206b09bd06d8f2a93ab41a9376b63116b518d7a2a1eaf061be3d3752f85497d010000021c000000b02a45373256dae5ce1f24d34c51b90fa4d93fe18d48d979e40a0cb1434551e6bb60400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb6187ae98162350830e5333e41de8e10881566addbb3c43ffff6f01339654db1de1e6903670916fcbaa32e6ff004f1bae70159bf16e575fdf6a7f99d3caff9c37d000000b011995056113117cb5ec1544e69c660c360d83e3195e7012d8a50e3bc80022d3664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb630335117fdbcda57dd2004fa781cba03c532f2c87a8b37dabb163b468ffdf85a05bd1c8ac36d2eb7e2caf0f21cff0c0588f21bbb32838540283dcfb5eaaa09f8000000b02951b7ecacb8e9f256ae1b0703550a3f10a4831e5cadf9084e770bc9aab273b268400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb617876a3bb8130c551cbc85fc902a0b224ccb4f6cc798bf243b5a6dbfcaae3ed51d7584215ef500dedab7b7aab68db58138be60a7f94a7d1aec63f7c3155a50740000021c000000b010a5d110670f1bef964a9c091b625b5d983cdfc2a9bb8051cebb3e42e562ba2d6c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb62f3fd1d2539ade7c14a94cb529b8b49dfc9794598e5fb6feff8095ccf55e855104c99d45194b32dc1a5438acce9b069fc056bd4c465804646ca82a3c500a96ef000000b0285e38a70296ee168e3762c1b4f104d9480924af7082782c92e16650101300a970400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb61693eaf60df110795445cdb741c605bc842ff0fddb6d3e487fc4c846300ecbcc1c8204dbb4d305031240ff656829b01b702302390d1efc3f30ce52497abadd6b000000b00fb251cabced2013cdd3e3c3ccfe55f7cfa18153bd8fff76132598c94ac3472474400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb62e4c528ca978e2a04c32946fdb54af3833fc35eaa234362343eaf0535abf124803d61dff6f29370051dd806780370139f7bb5edd5a2c8388b11284c2b56b23e600000fa400000168000000b0276ab9615874f23ac5c0aa7c668cff737f6dc6408456f750d74bc0d675738da078400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb615a06bb063cf149d8bcf1571f3620056bb94928eef41bd6cc42f22cc956f58c31b8e85960ab1092749ca472019c5aab5a787a3ca20f37b637538accfe01b6a62000000b00ebed28512cb2438055d2b7e7e9a5092070622e4d1647e9a578ff34fb023d41b7c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb62d58d346ff56e6c483bbdc2a8cf0a9d26b60d77bb608b54788554ad9c01f9f3f02e29eb9c5073b248966c82231d2fbd42f20006e6e0102acf57cdf491acbb0dd00000168000000b026773a1bae52f65efd49f2371828fa0db6d267d1982b76751bb61b5cdad41a9780400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb614acec6ab9ad18c1c3585d2ca4fdfaf0f2f9342003163c9108997d52facfe5ba1a9b0650608f0d4b81538edacb61a54fdeec455b34c7fa87b9a30756457bf759000000b00dcb533f68a9285c3ce6733930364b2c3e6ac475e538fdbe9bfa4dd61584611284400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb62c6554015534eae8bb4523e53e8ca46ca2c5790cc9dd346bccbfa56025802c3601ef1f741ae53f48c0f00fdce36ef66e6684a1ff81d581d139e739cf802c3dd400000168000000b02583bad60430fa8334d339f1c9c4f4a7ee370962abfff599602075e34034a78e88400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb613b96d250f8b1ce5fae1a4e75699f58b2a5dd5b116eabbb54d03d7d9603072b119a7870ab66d116fb8dcd6957cfd9fea1650e6ec489c79abfe0d61dcaadc8450000000b00cd7d3f9be872c80746fbaf3e1d245c675cf6606f90d7ce2e064a85c7ae4ee098c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb62b71d4bbab12ef0cf2ce6b9ff0289f06da2a1a9dddb1b3901129ffe68ae0b92d00fba02e70c3436cf8795797950af1089de9439095aa00f57e519455e58ccacb00000168000000b024903b905a0efea76c5c81ac7b60ef42259baaf3bfd474bda48ad069a595348590400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb612c5eddf6569210a326aeca20835f02561c277422abf3ad9916e325fc590ffa818b407c50c4b1593f0661e502e999a844db5887d5c70f8d04277bc63103d1147000000b00be454b4146530a4abf902ae936e4060ad3407980ce1fc0724cf02e2e0457b0094400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb62a7e557600f0f3312a57b35aa1c499a1118ebc2ef18632b455945a6cf0414624000820e8c6a1479130029f5246a6eba2d54de521a97e8019c2bbeedc4aed57c200000168000000b0239cbc4aafed02cba3e5c9672cfce9dc5d004c84d3a8f3e1e8f52af00af5c17c98400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb611d26e99bb47252e69f4345cb9d1eabf992718d33e93b9fdd5d88ce62af18c9f17c0887f622919b827ef660ae035951e851a2a0e704577f486e216e9759d9e3e000000b00af0d56e6a4334c8e3824a69450a3afae498a92920b67b2b69395d6945a607f79c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb6298ad63056cef75561e0fb155360943b48f35dc0055ab1d899feb4f355a1d31b2f78f015fdb0ebdf1fdc2cc379c43e9a34e66efb370c6fcf4b083ef6a04de4ba00000168000000b022a93d0505cb06efdb6f1121de98e4769464ee15e77d73062d5f857670564e73a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb610deef5411252952a17d7c176b6de559d08bba64526839221a42e76c9052199616cd0939b8071ddc5f78adc591d18fb8bc7ecb9f8419f718cb4c716fdafe2b35000000b009fd5628c02138ed1b0b9223f6a635951bfd4aba348afa4fada3b7efab0694eea4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6289756eaacacfb79996a42d004fc8ed58057ff51192f30fcde690f79bb0260122e8570d0538ef0035765747e2b6039346c4b108c4ae0eef38f72997d05ae71b100000168000000b021b5bdbf5ba90b1412f858dc9034df10cbc98fa6fb51f22a71c9dffcd5b6db6aa8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb60feb700e67032d76d906c3d21d09dff407f05bf5663cb8465ead41f2f5b2a68d15d989f40de522009701f580436d8a52f3e36d3097ee763d0fb6cbf6405eb82c000000b00909d6e315ff3d115294d9dea842302f5361ec4b485f7973f20e1276106721e5ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb627a3d7a5028aff9dd0f38a8ab698896fb7bca0e22d03b02122d36a002062ed092d91f18aa96cf4278eeebc38dcfc33cea3afb21d5eb56e17d3dcf4036b0efea800000168000000b020c23e79b1870f384a81a09741d0d9ab032e31380f26714eb6343a833b176861b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb60ef7f0c8bce1319b10900b8ccea5da8e3f54fd867a11376aa3179c795b13338414e60aae63c32624ce8b3d3af50984ed2b480ec1abc2f5615421267ca5bf4523000000b00816579d6bdd41358a1e219959de2ac98ac68ddc5c33f89836786cfc75c7aedcb4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb626b0585f586903c2087cd24568348409ef21427340d82f45673dc48685c37a002c9e7244ff4af84bc67803f38e982e68db1453ae7289ed3c18474e89d06f8b9f00000168000000b01fcebf340765135c820ae851f36cd4453a92d2c922faf072fa9e9509a077f558b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb60e04718312bf35bf481953478041d52876b99f178de5b68ee781f6ffc073c07b13f28b68b9a12a49061484f5a6a57f8762acb052bf977485988b81030b1fd21a000000b00722d857c1bb4559c1a769540b7a2563c22b2f6d700877bc7ae2c782db283bd3bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb625bcd919ae4707e640061a0019d07ea42685e40454acae69aba81f0ceb2406f72baaf2ff5528fc6ffe014bae403429031278f53f865e6c605cb1a91035d0189600000168000000b01edb3fee5d431780b994300ca508cedf71f7745a36cf6f973f08ef9005d8824fc0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb60d10f23d689d39e37fa29b0231ddcfc2ae1e40a8a1ba35b32bec518625d44d7212ff0c230f7f2e6d3d9dccb058417a219a1151e3d36bf3a9dcf5db8970805f11000000b0062f59121799497df930b10ebd161ffdf98fd0fe83dcf6e0bf4d22094088c8cac4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb624c959d404250c0a778f61bacb6c793e5dea859568812d8df0127993508493ee2ab773b9ab070094358a9368f1d0239d49dd96d09a32eb84a11c03969b30a58d00000168000000b01de7c0a8b3211ba4f11d77c756a4c979a95c15eb4aa3eebb83734a166b390f46c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb60c1d72f7be7b3e07b72be2bce379ca5ce582e239b58eb4d77056ac0c8b34da69120b8cdd655d32917527146b09dd74bbd175f374e74072ce2160360fd5e0ec08000000b0053bd9cc6d774da230b9f8c96eb21a9830f4728f97b1760503b77c8fa5e955c1cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb623d5da8e5a03102eaf18a9757d0873d8954f27267c55acb2347cd419b5e520e529c3f47400e504b86d13db23a36c1e3781423861ae076aa8e5865e1d00913284380000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020100000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000000000000000000000000000000000020400000000000000000000000000000000000000000000000000000000000002050000000000000000000000000000000000000000000000000000000000000206000000000000000000000000000000000000000000000000000000000000020700000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000209000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020d000000000000000000000000000000000000000000000000000000000000020e000000000000000000000000000000000000000000000000000000000000020f0000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000021100000000000000000000000000000000000000000000000000000000000002120000000000000000000000000000000000000000000000000000000000000213000000000000000000000000000000000000000000000000000000000000021400000000000000000000000000000000000000000000000000000000000002150000000000000000000000000000000000000000000000000000000000000216000000000000000000000000000000000000000000000000000000000000021700000000000000000000000000000000000000000000000000000000000002180000000000000000000000000000000000000000000000000000000000000219000000000000000000000000000000000000000000000000000000000000021a000000000000000000000000000000000000000000000000000000000000021b000000000000000000000000000000000000000000000000000000000000021c000000000000000000000000000000000000000000000000000000000000021d000000000000000000000000000000000000000000000000000000000000021e000000000000000000000000000000000000000000000000000000000000021f0000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000022100000000000000000000000000000000000000000000000000000000000002220000000000000000000000000000000000000000000000000000000000000223000000000000000000000000000000000000000000000000000000000000022400000000000000000000000000000000000000000000000000000000000002250000000000000000000000000000000000000000000000000000000000000226000000000000000000000000000000000000000000000000000000000000022700000000000000000000000000000000000000000000000000000000000002280000000000000000000000000000000000000000000000000000000000000229000000000000000000000000000000000000000000000000000000000000022a000000000000000000000000000000000000000000000000000000000000022b000000000000000000000000000000000000000000000000000000000000022c000000000000000000000000000000000000000000000000000000000000022d000000000000000000000000000000000000000000000000000000000000022e000000000000000000000000000000000000000000000000000000000000022f00000000000000000000000000000000000000000000000000000000000002300000000000000000000000000000000000000000000000000000000000000231000000000000000000000000000000000000000000000000000000000000023200000000000000000000000000000000000000000000000000000000000002330000000000000000000000000000000000000000000000000000000000000234000000000000000000000000000000000000000000000000000000000000023500000000000000000000000000000000000000000000000000000000000002360000000000000000000000000000000000000000000000000000000000000237370000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030100000000000000000000000000000000000000000000000000000000000003020000000000000000000000000000000000000000000000000000000000000303000000000000000000000000000000000000000000000000000000000000030400000000000000000000000000000000000000000000000000000000000003050000000000000000000000000000000000000000000000000000000000000306000000000000000000000000000000000000000000000000000000000000030700000000000000000000000000000000000000000000000000000000000003080000000000000000000000000000000000000000000000000000000000000309000000000000000000000000000000000000000000000000000000000000030a000000000000000000000000000000000000000000000000000000000000030b000000000000000000000000000000000000000000000000000000000000030c000000000000000000000000000000000000000000000000000000000000030d000000000000000000000000000000000000000000000000000000000000030e000000000000000000000000000000000000000000000000000000000000030f0000000000000000000000000000000000000000000000000000000000000310000000000000000000000000000000000000000000000000000000000000031100000000000000000000000000000000000000000000000000000000000003120000000000000000000000000000000000000000000000000000000000000313000000000000000000000000000000000000000000000000000000000000031400000000000000000000000000000000000000000000000000000000000003150000000000000000000000000000000000000000000000000000000000000316000000000000000000000000000000000000000000000000000000000000031700000000000000000000000000000000000000000000000000000000000003180000000000000000000000000000000000000000000000000000000000000319000000000000000000000000000000000000000000000000000000000000031a000000000000000000000000000000000000000000000000000000000000031b000000000000000000000000000000000000000000000000000000000000031c000000000000000000000000000000000000000000000000000000000000031d000000000000000000000000000000000000000000000000000000000000031e000000000000000000000000000000000000000000000000000000000000031f0000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032100000000000000000000000000000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000323000000000000000000000000000000000000000000000000000000000000032400000000000000000000000000000000000000000000000000000000000003250000000000000000000000000000000000000000000000000000000000000326000000000000000000000000000000000000000000000000000000000000032700000000000000000000000000000000000000000000000000000000000003280000000000000000000000000000000000000000000000000000000000000329000000000000000000000000000000000000000000000000000000000000032a000000000000000000000000000000000000000000000000000000000000032b000000000000000000000000000000000000000000000000000000000000032c000000000000000000000000000000000000000000000000000000000000032d000000000000000000000000000000000000000000000000000000000000032e000000000000000000000000000000000000000000000000000000000000032f00000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000331000000000000000000000000000000000000000000000000000000000000033200000000000000000000000000000000000000000000000000000000000003330000000000000000000000000000000000000000000000000000000000000334000000000000000000000000000000000000000000000000000000000000033500000000000000000000000000000000000000000000000000000000000003360200000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000401100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060a0000000000000000000000000000000000000000000000000000000000000601000000000000000000000000000000000000000000000000000000000000060b0000000000000000000000000000000000000000000000000000000000000602000000000000000000000000000000000000000000000000000000000000060c0000000000000000000000000000000000000000000000000000000000000603000000000000000000000000000000000000000000000000000000000000060d0000000000000000000000000000000000000000000000000000000000000604000000000000000000000000000000000000000000000000000000000000060e0000000000000000000000000000000000000000000000000000000000000605000000000000000000000000000000000000000000000000000000000000060f00000000000000000000000000000000000000000000000000000000000006060000000000000000000000000000000000000000000000000000000000000610000000000000000000000000000000000000000000000000000000000000060700000000000000000000000000000000000000000000000000000000000006110000000000000000000000000000000000000000000000000000000000000608000000000000000000000000000000000000000000000000000000000000061200000000000000000000000000000000000000000000000000000000000006090000000000000000000000000000000000000000000000000000000000000613000000000000000000000000000000000000000000000000000000000000060a0000000000000000000000000000000000000000000000000000000000000614000000000000000000000000000000000000000000000000000000000000060b0000000000000000000000000000000000000000000000000000000000000615000000000000000000000000000000000000000000000000000000000000060c0000000000000000000000000000000000000000000000000000000000000616000000000000000000000000000000000000000000000000000000000000060d0000000000000000000000000000000000000000000000000000000000000617000000000000000000000000000000000000000000000000000000000000060e0000000000000000000000000000000000000000000000000000000000000618000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000000619000011000000021c000000b01cf4416308ff1fc928a6bf820840c413e0c0b77c5e786ddfc7dda49cd0999c3dd0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb60b29f3b21459422beeb52a779515c4f71ce783cac96333fbb4c10692f095676011180d97bb3b36b5acb05c25bb796f5608da9505fb14f1f265ca90963b4178ff000000b004485a86c35551c668434084204e153268591420ab85f5294821d7160b49e2b8d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb622e25b48afe11452e6a1f1302ea46e72ccb3c8b7902a2bd678e72ea01b45addc28d0752e56c308dca49d22de550818d1b8a6d9f2c1dbe9cd29f0b8a365f1bf7b000000b01c00c21d5edd23ed6030073cb9dcbeae1825590d724ced040c47ff2335fa2934d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb60a36746c6a374650263e723246b1bf91544c255bdd37b31ff92b611955f5f45710248e5211193ad9e439a3e06d1569f0403f36970ee97116aa34eb1ca0a205f60000021c000000b00354db41193355ea9fcc883ed1ea0fcc9fbdb5b1bf5a744d8c8c319c70aa6fafdc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb621eedc0305bf18771e2b38eae040690d04186a48a3feaafabd51892680a63ad327dcf5e8aca10d00dc266a9906a4136bf00b7b83d5b068f16e5b1329cb524c72000000b01b0d42d7b4bb281197b94ef76b78b9484f89fa9e86216c2850b259a99b5ab62be0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb60942f526c0154a745dc7b9ecf84dba2b8bb0c6ecf10c32443d95bb9fbb56814e0f310f0c66f73efe1bc2eb9b1eb1648a77a3d82822bdf03aee9f45a3060292ed000000b002615bfb6f115a0ed755cff983860a66d7225742d32ef371d0f68c22d60afca6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb620fb5cbd5b9d1c9b55b480a591dc63a73b7d0bd9b7d32a1f01bbe3ace606c7ca26e976a3027f112513afb253b8400e0627701d14e984e815b2c56db030b2d9690000021c000000b01a19c3920a992c35cf4296b21d14b3e286ee9c2f99f5eb4c951cb43000bb4322e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6084f75e115f34e98955101a7a9e9b4c5c315687e04e0b1688200162620b70e450e3d8fc6bcd54322534c3355d04d5f24af0879b936926f5f3309a0296b631fe4000000b0016ddcb5c4ef5e330edf17b4352205010e86f8d3e70372961560e6a93b6b899dec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb62007dd77b17b20bf8d3dc86043785e4172e1ad6acba7a94346263e334b6754c125f5f75d585d15494b38fa0e69dc08a05ed4bea5fd596739f72fc83696136660000000b01926444c6077305a06cbde6cceb0ae7cbe533dc0adca6a70d9870eb6661bd019f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6075bf69b6bd152bcccda49625b85af5ffa7a0a0f18b5308cc66a70ac86179b3c0d4a108112b347468ad57b1081e959bee66d1b4a4a66ee837773faafd0c3acdb0000021c000000b0007a5d701acd625746685f6ee6bdff9b45eb9a64fad7f1ba59cb412fa0cc1694f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb61f145e32075924e3c4c7101af51458dbaa464efbdf7c28678a9098b9b0c7e1b825027817ae3b196d82c241c91b78033a96396037112de65e3b9a22bcfb73f357000000b01832c506b655347e3e552627804ca916f5b7df51c19ee9951df1693ccb7c5d10f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb606687755c1af56e10463911d0d21a9fa31deaba02c89afb10ad4cb32eb7828330c56913b68914b6ac25ec2cb338554591dd1bcdb5e3b6da7bbde5536362439d2000000b02feb2c9d51dd06a53641ece019db5292a584243e8865e16fe2179149f62ca38cfc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb61e20deec5d372907fc5057d5a6b05375e1aaf08cf350a78bcefaf34016286eaf240ef8d204191d91ba4b8983cd13fdd4cd9e01c82502658280047d4360d4804e0000021c000000b017a6cfd6dc3bc39624f9967152205e32c37c048c927b0904929824f9a605f73800410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb60574f810178d5b053becd8d7bebda49469434d31405e2ed54f3f25b950d8b52a0b6311f5be6f4f8ef9e80a85e5214ef355365e6c720feccc0048afbc9b84c6c9000000b02f5f376d77c395bd1ce65d29ebaf07ae73484979594200df56be4d06d0b63db404410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb61d94e9bc831db81fe2f4c81f78840891af6f15c7c42cc6fb43a1aefcf0b208d7238303a229ffaca9a0eff9cd9ee7b2f09b622702f5de84f1f4ab39003b5e1a76000000b016b350913219c7ba5c82de2c03bc58ccfae0a61da64f8828d7027f800b66842f08410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb604e902e03d73ea1d22914921909159b03707726c113a4e44c3e5e1762b624f520ad71cc5e455dea6e08c7acfb6f5040f22fa83a742ec0c3b74ef6b79760e60f10000021c000000b02e6bb827cda199e1546fa4e49d4b0248aaaceb0a6d1680039b28a78d3616caab0c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb61ca16a76d8fbbc441a7e0fda2a20032be6d3b758d801461f880c0983561295ce228f845c7fddb0cdd87941885083ad8ad2c6c89409b3041639159386a0bea76d000000b015bfd14b87f7cbde940c25e6b5585367324547aeba24074d1b6cda0670c7112610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb603f5839a9351ee415a1a90dc422d544a6e6c13fd250ecd6908503bfc90c2dc4909e39d803a33e2cb1815c28a6890fea95a5f253856c08b5fb959c5ffdb6eede8000000b02d7838e2237f9e058bf8ec9f4ee6fce2e2118c9b80eaff27df9302139b7757a214410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb61badeb312ed9c06852075794dbbbfdc61e3858e9ebd5c543cc766409bb7322c5219c0516d5bbb4f210028943021fa8250a2b6a251d87833a7d7fee0d061f34640000021c000000b014cc5205ddd5d002cb956da166f44e0169a9e93fcdf886715fd7348cd6279e1d18410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb603020454e92ff26591a3d896f3c94ee4a5d0b58e38e34c8d4cba9682f623694008f01e3a9011e6ef4f9f0a451a2cf94391c3c6c96a950a83fdc4208640cf7adf000000b02c84b99c795da229c382345a0082f77d19762e2c94bf7e4c23fd5c9a00d7e4991c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61aba6beb84b7c48c89909f4f8d57f860559cfa7affaa446810e0be9020d3afbc20a885d12b99b916478bd0fdb3bba2bf41900bb6315c025ec1ea48936b7fc15b000000b013d8d2c033b3d427031eb55c1890489ba10e8ad0e1cd0595a4418f133b882b1420410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb6020e850f3f0df689c92d2051a565497edd35571f4cb7cbb19124f1095b83f63707fc9ef4e5efeb13872851ffcbc8f3ddc928685a7e6989a8422e7b0ca63007d60000021c000000b02b913a56cf3ba64dfb0b7c14b21ef21750dacfbda893fd706867b7206638719024410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb619c6eca5da95c8b0c119e70a3ef3f2fa8d019c0c137ec38c554b191686343cb31fb5068b8177bd3a7f1518b865579d5978f4ad47453081830654a319d0e04e52000000b012e5537a8991d84b3aa7fd16ca2c4335d8732c61f5a184b9e8abe999a0e8b80b28410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb6011b05c994ebfaae00b6680c570144191499f8b0608c4ad5d58f4b8fc0e4832e07091faf3bcdef37beb199ba7d64ee78008d09eb923e08cc8698d5930b9094cd000000b02a9dbb112519aa723294c3cf63baecb1883f714ebc687c94acd211a6cb98fe872c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb618d36d603073ccd4f8a32ec4f08fed94c4663d9d275342b099b5739ceb94c9aa1ec18745d755c15eb69e607316f397f3b0594ed8590500a74abefda03640db4900000fa400000168000000b011f1d434df6fdc6f723144d17bc83dd00fd7cdf3097603de2d1644200649450230410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb600278683eac9fed2383fafc7089d3eb34bfe9a417460c9fa19f9a616264510250615a06991abf35bf63ae1752f00e91237f1ab7ca61287f0cb03301970f121c4000000b029aa3bcb7af7ae966a1e0b8a1556e74bbfa412dfd03cfbb8f13c6c2d30f98b7e34410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb617dfee1a8651d0f9302c767fa22be82efbcadf2e3b27c1d4de1fce2350f556a11dce08002d33c582ee27a82dc88f928de7bdf0696cd97fcb8f2958269ba1684000000168000000b010fe54ef354de093a9ba8c8c2d64386a473c6f841d4a830271809ea66ba9d1f938410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb62f9855b121d9a32028193d383bba91aaab97241b01eeb9afa245f6307ba59d1d05222123e789f7802dc4292fe09ce3ac6f564d0db9e707150f6d8a9fd651aebb000000b028b6bc85d0d5b2baa1a75344c6f2e1e5f708b470e4117add35a6c6b3965a18753c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb616ec6ed4dc2fd51d67b5be3a53c7e2c9332f80bf4efc40f9228a28a9b655e3981cda88ba8311c9a725b0efe87a2b8d281f2291fa80adfeefd393b2ad0101f53700000168000000b0100ad5a98b2be4b7e143d446df0033047ea11115311f0226b5eaf92cd10a5ef040410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb62ea4d66b77b7a7445fa284f2ed568c44e2fbc5ac15c338d3e6b050b6e1062a14042ea1de3d67fba4654d70ea9238de46a6baee9ecdbb863953d7e5263bb23bb2000000b027c33d4026b3b6ded9309aff788edc802e6d5601f7e5fa017a112139fbbaa56c44410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb615f8ef8f320dd9419f3f05f50563dd636a94225062d0c01d66f483301bb6708f1be70974d8efcdcb5d3a37a32bc787c25687338b94827e1417fe0d336662822e00000168000000b00f175663e109e8dc18cd1c01909c2d9eb605b2a644f3814afa5553b3366aebe748410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb62db15725cd95ab68972bccad9ef286df1a60673d2997b7f82b1aab3d4666b70b033b22989345ffc89cd6b8a543d4d8e0de1f902fe190055d98423faca112c8a9000000b026cfbdfa7c91bb0310b9e2ba2a2ad71a65d1f7930bba7925be7b7bc0611b32634c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb61505704987ebdd65d6c84dafb6ffd7fda1f8c3e176a53f41ab5eddb68116fd861af38a2f2ecdd1ef94c37f5ddd63825c8debd51ca856fd385c6867b9cbc30f2500000168000000b00e23d71e36e7ed00505663bc42382838ed6a543758c8006f3ebfae399bcb78de50410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb62cbdd7e02373af8cceb51468508e817951c508ce3d6c371c6f8505c3abc744020247a352e92403ecd460005ff570d37b158431c0f5648481dcac9a33067355a0000000b025dc3eb4d26fbf2748432a74dbc6d1b49d3699241f8ef84a02e5d646c67bbf5a54410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb61411f103ddc9e18a0e51956a689bd297d95d65728a79be65efc9383ce6778a7d1a000ae984abd613cc4cc7188eff7cf6c55076adbc2b7c5ca0d2c24031239c1c00000168000000b00d3057d88cc5f12487dfab76f3d422d324cef5c86c9c7f93832a08c0012c05d558410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb62bca589a7951b3b1063e5c23022a7c138929aa5f5140b640b3ef604a1127d0f90154240d3f0208110be9481aa70cce154ce8d352093903a62116f4b96bd3e297000000b024e8bf6f284dc34b7fcc722f8d62cc4ed49b3ab53363776e475030cd2bdc4c515c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb6131e71be33a7e5ae45dadd251a37cd3210c207039e4e3d8a343392c34bd81774190c8ba3da89da3803d60ed3409b7790fcb5183ecffffb80e53d1cc69684291300000168000000b00c3cd892e2a3f548bf68f331a5701d6d5c3397598070feb7c7946346668c92cc60410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb62ad6d954cf2fb7d53dc7a3ddb3c676adc08e4bf065153564f859bad076885df00060a4c794e00c3543728fd558a8c8af844d74e31d0d82ca65814f3fd1346f8e000000b023f540297e2bc76fb755b9ea3efec6e90bffdc464737f6928bba8b53913cd94864410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb6122af2788985e9d27d6424dfcbd3c7cc4826a894b222bcae789ded49b138a46b18190c5e3067de5c3b5f568df237722b3419b9cfe3d47aa529a7774cfbe4b60a00000168000000b00b49594d3881f96cf6f23aec570c1807939838ea94457ddc0bfebdcccbed1fc368410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb629e35a0f250dbbf97550eb9865627147f7f2ed8178e9b4893cc41556dbe8eae72fd173f4cbefb083334c1d468bc61ba6e3e5febcaa9b727fedcd9f5a2694fc86000000b02301c0e3d409cb93eedf01a4f09ac18343647dd75b0c75b6d024e5d9f69d663f6c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb611377332df63edf6b4ed6c9a7d6fc2667f8b4a25c5f73bd2bd0847d01699316217258d188645e28072e89e48a3d36cc56b7e5b60f7a8f9c96e11d1d36145430100000168000000b00a55da078e5ffd912e7b82a708a812a1cafcda7ba819fd0050691853314dacba70410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb628efdac97aebc01dacda335316fe6be22f578f128cbe33ad812e6fdd414977de2eddf4af21cdb4a76ad565013d6216411b4aa04dbe6ff1a43237f9e08bf5897d000000b0220e419e29e7cfb82668495fa236bc1d7ac91f686ee0f4db148f40605bfdf33674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb61043f3ed3541f21aec76b4552f0bbd00b6efebb6d9cbbaf70172a2567bf9be5916320dd2dc23e6a4aa71e603556f675fa2e2fcf20b7d78edb27c2c59c6a5cff800000168000000b009625ac1e43e01b56604ca61ba440d3c02617c0cbbee7c2494d372d996ae39b178410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb627fc5b83d0c9c441e4637b0dc89a667c66bc30a3a092b2d1c598ca63a6aa04d52dea756977abb8cba25eacbbeefe10db52af41ded24470c876a25466f1561674000000b0211ac2587fc5d3dc5df1911a53d2b6b7b22dc0f982b573ff58f99ae6c15e802d7c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb60f5074a78b1ff63f23fffc0fe0a7b79aee548d47eda03a1b45dcfcdce15a4b50153e8e8d3201eac8e1fb2dbe070b61f9da479e831f51f811f6e686e02c065cef00000168000000b0086edb7c3a1c05d99d8e121c6be007d639c61d9dcfc2fb48d93dcd5ffc0ec6a880410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb62708dc3e26a7c8661becc2c87a3661169e20d234b46731f60a0324ea0c0a91cc2cf6f623cd89bcefd9e7f476a09a0b758a13e36fe618efecbb0caeed56b6a36b000000b020274312d5a3d800957ad8d5056eb151e992628a9689f3239d63f56d26bf0d2484410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb60e5cf561e0fdfa635b8943ca9243b23525b92ed90174b93f8a47576346bad847144b0f4787dfeeed19847578b8a75c9411ac4014332677363b50e1669166e9e6", + "archive": "0x23464816bf0c3a39a98fcfba3153cdc11638cf5ac8fa184581a066f109dbadf8", + "body": "0x00000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004380000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000001420000000000000000000000000000000000000000000000000000000000000143000000000000000000000000000000000000000000000000000000000000014400000000000000000000000000000000000000000000000000000000000001450000000000000000000000000000000000000000000000000000000000000146000000000000000000000000000000000000000000000000000000000000014700000000000000000000000000000000000000000000000000000000000001480000000000000000000000000000000000000000000000000000000000000149000000000000000000000000000000000000000000000000000000000000014a000000000000000000000000000000000000000000000000000000000000014b000000000000000000000000000000000000000000000000000000000000014c000000000000000000000000000000000000000000000000000000000000014d000000000000000000000000000000000000000000000000000000000000014e000000000000000000000000000000000000000000000000000000000000014f0000000000000000000000000000000000000000000000000000000000000150000000000000000000000000000000000000000000000000000000000000015100000000000000000000000000000000000000000000000000000000000001520000000000000000000000000000000000000000000000000000000000000153000000000000000000000000000000000000000000000000000000000000015400000000000000000000000000000000000000000000000000000000000001550000000000000000000000000000000000000000000000000000000000000156000000000000000000000000000000000000000000000000000000000000015700000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000159000000000000000000000000000000000000000000000000000000000000015a000000000000000000000000000000000000000000000000000000000000015b000000000000000000000000000000000000000000000000000000000000015c000000000000000000000000000000000000000000000000000000000000015d000000000000000000000000000000000000000000000000000000000000015e000000000000000000000000000000000000000000000000000000000000015f0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000001620000000000000000000000000000000000000000000000000000000000000163000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000001650000000000000000000000000000000000000000000000000000000000000166000000000000000000000000000000000000000000000000000000000000016700000000000000000000000000000000000000000000000000000000000001680000000000000000000000000000000000000000000000000000000000000169000000000000000000000000000000000000000000000000000000000000016a000000000000000000000000000000000000000000000000000000000000016b000000000000000000000000000000000000000000000000000000000000016c000000000000000000000000000000000000000000000000000000000000016d000000000000000000000000000000000000000000000000000000000000016e000000000000000000000000000000000000000000000000000000000000016f00000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000171000000000000000000000000000000000000000000000000000000000000017200000000000000000000000000000000000000000000000000000000000001730000000000000000000000000000000000000000000000000000000000000174000000000000000000000000000000000000000000000000000000000000017500000000000000000000000000000000000000000000000000000000000001760000000000000000000000000000000000000000000000000000000000000177370000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024100000000000000000000000000000000000000000000000000000000000002420000000000000000000000000000000000000000000000000000000000000243000000000000000000000000000000000000000000000000000000000000024400000000000000000000000000000000000000000000000000000000000002450000000000000000000000000000000000000000000000000000000000000246000000000000000000000000000000000000000000000000000000000000024700000000000000000000000000000000000000000000000000000000000002480000000000000000000000000000000000000000000000000000000000000249000000000000000000000000000000000000000000000000000000000000024a000000000000000000000000000000000000000000000000000000000000024b000000000000000000000000000000000000000000000000000000000000024c000000000000000000000000000000000000000000000000000000000000024d000000000000000000000000000000000000000000000000000000000000024e000000000000000000000000000000000000000000000000000000000000024f0000000000000000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000000000000000000025100000000000000000000000000000000000000000000000000000000000002520000000000000000000000000000000000000000000000000000000000000253000000000000000000000000000000000000000000000000000000000000025400000000000000000000000000000000000000000000000000000000000002550000000000000000000000000000000000000000000000000000000000000256000000000000000000000000000000000000000000000000000000000000025700000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000259000000000000000000000000000000000000000000000000000000000000025a000000000000000000000000000000000000000000000000000000000000025b000000000000000000000000000000000000000000000000000000000000025c000000000000000000000000000000000000000000000000000000000000025d000000000000000000000000000000000000000000000000000000000000025e000000000000000000000000000000000000000000000000000000000000025f0000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026100000000000000000000000000000000000000000000000000000000000002620000000000000000000000000000000000000000000000000000000000000263000000000000000000000000000000000000000000000000000000000000026400000000000000000000000000000000000000000000000000000000000002650000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000026700000000000000000000000000000000000000000000000000000000000002680000000000000000000000000000000000000000000000000000000000000269000000000000000000000000000000000000000000000000000000000000026a000000000000000000000000000000000000000000000000000000000000026b000000000000000000000000000000000000000000000000000000000000026c000000000000000000000000000000000000000000000000000000000000026d000000000000000000000000000000000000000000000000000000000000026e000000000000000000000000000000000000000000000000000000000000026f00000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000027200000000000000000000000000000000000000000000000000000000000002730000000000000000000000000000000000000000000000000000000000000274000000000000000000000000000000000000000000000000000000000000027500000000000000000000000000000000000000000000000000000000000002760200000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000341100000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000054a0000000000000000000000000000000000000000000000000000000000000541000000000000000000000000000000000000000000000000000000000000054b0000000000000000000000000000000000000000000000000000000000000542000000000000000000000000000000000000000000000000000000000000054c0000000000000000000000000000000000000000000000000000000000000543000000000000000000000000000000000000000000000000000000000000054d0000000000000000000000000000000000000000000000000000000000000544000000000000000000000000000000000000000000000000000000000000054e0000000000000000000000000000000000000000000000000000000000000545000000000000000000000000000000000000000000000000000000000000054f00000000000000000000000000000000000000000000000000000000000005460000000000000000000000000000000000000000000000000000000000000550000000000000000000000000000000000000000000000000000000000000054700000000000000000000000000000000000000000000000000000000000005510000000000000000000000000000000000000000000000000000000000000548000000000000000000000000000000000000000000000000000000000000055200000000000000000000000000000000000000000000000000000000000005490000000000000000000000000000000000000000000000000000000000000553000000000000000000000000000000000000000000000000000000000000054a0000000000000000000000000000000000000000000000000000000000000554000000000000000000000000000000000000000000000000000000000000054b0000000000000000000000000000000000000000000000000000000000000555000000000000000000000000000000000000000000000000000000000000054c0000000000000000000000000000000000000000000000000000000000000556000000000000000000000000000000000000000000000000000000000000054d0000000000000000000000000000000000000000000000000000000000000557000000000000000000000000000000000000000000000000000000000000054e0000000000000000000000000000000000000000000000000000000000000558000000000000000000000000000000000000000000000000000000000000054f0000000000000000000000000000000000000000000000000000000000000559000011000000021c000000b02d622c8b62e54bf51a1fd35b67456b229dae3bc6126977f1b2d88662a3418347a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb6a83e0eb61b97deda6e3f6e57e02e3e50f41a6c05d9d508147d543e0d9fbbe858c33d4e6a2185f8c0152162e19e296fff1a7e1664c5c8194faf05fc0450c5725c0de96009000000b014b645af1d3b7df259bc545d7f52bc412546986a5f76ff3b331cb8dbddf1c9c2ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb6ac3e0eb602ebf7fe2895a0551fcabf530c27bd24616d64b8ca61c55720001ad1fded94e508da11e3cf7794deddc5f101328b67834d6075f3fc13834dd109a4d54899a684000000b02c6ead45b8c3501951a91b1618e165bcd512dd57263df715f742e0e908a2103eb03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb6b03e0eb61aa45f94c41d727c17b7860ba5b666a01139a9a59128bd31e42642df289ddb612092797a6aff6705d5b2b7b9cc1a10fefd2cbae0c2da7b28952fcce27349ed000000021c000000b013c2c6697319821691459c1830eeb6db5cab39fb734b7e5f77871362435256b9b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb6b43e0eb601f878b87e73a4795754070dbdc3b7be98d20649de36447b646a7558634e21dc07e6929e25559903154f38bbe427621d84c517850fe802721573ff5badfa337b000000b02b7b2e000ea1543d893262d0ca7d60570c777ee83a12763a3bad3b6f6e029d35b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb6b83e0eb619b0e04f19fb76a04f40cdc65752613a489e4b36a4fd3c5628909d658dfe68581f9efa34c0dd6b2a0d3bff747db60b9934915c71d6aefa4cd99a2768d8aa79f7000000b012cf4723c8f7863ac8cee3d2e28ab175940fdb8c871ffd83bbf16de8a8b2e3b0bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb6bc3e0eb60104f972d451a89d8edd4ec86f5fb258d036a7daf20ac39fa8d4cfdec8aeaed306f313587b339d274cd8807695c35cb7bc29b91623bc819659de59e2135ac0720000021c000000b02a87aeba647f5861c0bbaa8b7c195af143dc20794de6f55e801795f5d3632a2cc03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb6c03e0eb618bd61096fd97ac486ca158108ee5bd48002ecc7b8d1bb7a6cfaf7ebf35ef54f1eab7aef16bb6f4e44c5472f2f5206336bf5fe02ea8379711e0481ef3e0b06ee000000b011dbc7de1ed58a5f00582b8d9426ac0fcb747d1d9af47ca8005bc86f0e1370a7c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb6c43e0eb600117a2d2a2facc1c666968320fbacf3079b496c05df42c3ed3f2a652e0f3bca05ff9412d111a14b8461c831475f5751f38e5aa7379100ba9e48b46878bb4d69000000b029942f74ba5d5c85f844f2462db5558b7b40c20a61bb7482c481f07c38c3b723c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb6c83e0eb617c9e1c3c5b77ee8be535d3bba8a566eb7678e58cca63a9eb165527258bf82461db7fba96c9973727c4e8ee9e0ee00cda35a9f93fe57f895626edc75a36b93e50000021c000000b010e8489874b38e8337e1734845c2a6aa02d91eaeaec8fbcc44c622f57373fd9ecc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb6cc3e0eb62f82495a613f510fb64023f45418ffea6733d345936d3279758b7a7f836fc8c2050c14cd26efa56fbbeb0febf8fb51ec2af2fc384b657fdee2b30eeede1bda60000000b028a0b02f103b60aa2fce3a00df515025b2a5639b758ff3a708ec4b029e24441ad03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb6d03e0eb616d6627e1b95830cf5dca4f66c265108eecc2fe9e07ab9c2f5cfacf8be200f3d1cc47c63c2777796b3d7d6a49289fb67dabf4125122c77b9a6d936fc08cc20dc000000b00ff4c952ca9192a76f6abb02f75ea1443a3dc03fc29d7af089307d7bd8d48a95d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb6d43e0eb62e8eca14b71d5533edc96baf05b4fa849e9874d6a741b19db9f5d505e8d055b9041895877ccda993f37457a6aa974c8662579dc95f39ff03271d6975437c67570000021c000000b027ad30e9661964ce675781bb90ed4abfea0a052c896472cb4d56a5890384d111d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb6d83e0eb615e2e338717387312d65ecb11dc24ba32630d17af44f38e73a3a077f23809c341bd0fd1e18557bbaeb611e5f4425f6021223e2b62600f6ddeb4391826e2cadd3000000b00f014a0d206f96cba6f402bda8fa9bde71a261d0d671fa14cd9ad8023e35178cdc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb6dc3e0eb62d9b4acf0cfb59582552b369b750f51ed5fd1667bb1630c1fe602f8c4e30e2b003251641d2abadb82afd9f615c33472099bc3f5a730e7e276b87c3fba8dcf44e000000b026b9b1a3bbf768f29ee0c9764289455a216ea6bd9d38f1ef91c1000f68e55e08e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb6e03e0eb614ef63f2c7518b5564ef346bcf5e463d5d95730c0823b80b7ea4620588e1292b1add7dd86e337fdf22ea6619f5c1f09c4988844739d576022fadec08d38d3aca0000021c000000b00e0dcac7764d9aefde7d4a785a969678a9070361ea46793912053288a395a483e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb6e43e0eb62ca7cb8962d95d7c5cdbfb2468ecefb90d61b7f8ceeaafe642ca8a12b3916fa7023196fc2889b1dc6286e71c0dcf41bad120e0eb86e2fd4baff21e820e3d8145000000b025c6325e11d56d16d66a1130f4253ff458d3484eb10d7113d62b5a95ce45eaffe83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb6e83e0eb613fbe4ad1d2f8f799c787c2680fa40d794fa149d1bf8372fc30ebc8bee41b62219e9fe92c41184035a73add4a75deb3680ed25d84da9f5267418468f38edc7c1000000b00d1a4b81cc2b9f14160692330c329112e06ba4f2fe1af85d566f8d0f08f6317aec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb6ec3e0eb62bb44c43b8b761a0946542df1a88ea5344c65989e2bf2f0a8734e49918f1fc9e013e17b67e67b6009a102ed6bf6b3c550885827c9ab77c6ff45c7908739e0e3c0000021c000000b024d2b31867b3713b0df358eba5c13a8e9037e9dfc4e1f0381a95b51c33a677f6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb6f03e0eb613086567730d939dd401c3e132963b71cc5eb62e2fccb6540779171253a2431918f67f4d19ef882791fcf58f58f9e5d0b851c769617e744ab882a1159e4e54b8000000b00c26cc3c2209a3384d8fd9edbdce8bad17d0468411ef77819ad9e7956e56be71f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb6f43e0eb62ac0ccfe0e9565c4cbee8a99cc24e4ed7c2afb1af693ae2ecb9f3f1f7e528995004a9870d445ba24d1997691710736ef3fea240dae8bfb9438c6d38ed8fe9b33000000b023df33d2bd91755f457ca0a6575d3528c79c8b70d8b66f5c5f000fa2990704edf83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb6f83e0eb61214e621c8eb97c20b8b0b9be432360c03c357bf43a135784be37198b902d010180300076fcd8c4bc9863d4a0a95e06aefb668fa7552f36efcecfb9c03aee1af0000021c000000b00b334cf677e7a75c851921a86f6a86474f34e81525c3f6a5df44421bd3b74b68fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb6fc3e0eb629cd4db8647369e90377d2547dc0df87b38f9cac0a682d53100999a5e3b3168c2fbb679e0b555e72c1730402a42489e69f82ade73c19eb49c11323a92e5f282b000000b023533ea2e37804772c2110f02930ea449560b0aba9928ecbd3a6cb5f73909f15003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6003f0eb6112166dc1ec99be64314535695ce30a63b27f9505775b49c904dcc1f1e635d07170f80c1c5ab9070010f8504bc31db05271b0a8b8927729341575622690f6ea6000000b00aa757c69dce36746bbd91f2413e3b631cf90d4ff6a0161553eafdd8ae40e590043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6043f0eb6294158888a59f900ea1c429e4f9494a38153c1e6db444cc284b05562be3cb0b42f2f726e313bed8aa817744c75f83f026d46d3220cf60ab935b9df6608e8c25300000fa400000168000000b0225fbf5d3956089b63aa58aadacce4deccc5523cbd670df0181125e5d8f12c0c083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6083f0eb6109571ac44b02afe29b8c3a067a1e5c208ec1e8b2851d40c04f487dbf8ecf72f16838b91eb921f87e7b3f54e8e059020f4df2fc65a039202b5fe11df439908ce000000b009b3d880f3ac3a98a346d9acf2da35fd545daee10a7495399855585f13a172870c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb60c3f0eb6284dd942e037fd2521a58a5901308f3db8b86377ef18cbe6c91aafe9239d3dab2e3bf3288719f1aedfa0bc072794399ca4ab74b320ca89dd7a2439ec6e494f4a00000168000000b0216c40178f340cbf9b33a0658c68df790429f3cdd13b8d145c7b806c3e51b903103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb6103f0eb60fa1f2669a8e2f2261420b5b193de05c4050c01c3c265330495ee2625e4d842615900c4c417023ac1f3d3d093fa18abb2c43d1576dd81126fa686c65a8f995c5000000b008c0593b498a3ebcdad02167a47630978bc250721e49145ddcbfb2e57901ff7e143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6143f0eb6275a59fd36160149592ed213b2cc89d7f01d050902ed4b0b0d850a6f88fdcaa22d4873e2dcf7f5d3172a03c1d9303436dc101644349f0901be8e9472d3a9dc4100000168000000b02078c0d1e51210e3d2bce8203e04da133b8e955ee5100c38a0e5daf2a3b245fa183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb6183f0eb60eae7320f06c334698cb5315cad9daf677b561ad4ffad2548dc93ce8c3ae111d149c8d06974e27d056c684c3f13d855563a872e881ac904b3ed2c6ec0e5a22bc000000b007ccd9f59f6842e11259692256122b31c326f203321d9382212a0d6bde628c751c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb61c3f0eb62666dab78bf4056d90b819ce646884722781a69a16c1ca2f51ef64f5ee5e57992c54f49d32d5f9f74eb34b7c8acc2ed11374b7d54873882602f8eef9390a693800000168000000b01f85418c3af015080a462fdaefa0d4ad72f336eff8e48b5ce55035790912d2f1203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb6203f0eb60dbaf3db464a376ad0549ad07c75d590af1a033e63cf5178d233976f290e9e1413a90dc0ed2c2bf48e4fcc7ea2d97fef9b0d147995810f6f833d217273baafb3000000b006d95aaff546470549e2b0dd07ae25cbfa8b939445f212a6659467f243c3196c243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb6243f0eb625735b71e1d20991c841618916047f0c5ee6482b2a9649539659bf7c53bee4902b61755788b3fe1b863c93373c68296b4ad959665c48074a4763497f9e6af62f00000168000000b01e91c24690ce192c41cf7795a13ccf47aa57d8810cb90a8129ba8fff6e735fe8283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb6283f0eb60cc774959c283b8f07dde28b2e11d02ae67ea4cf77a3d09d169df1f58e6f2b0b12b58e7b430a3018c5d9143954757a89d271b60aa9558e93c7a77bf8d91b3caa000000b005e5db6a4b244b29816bf897b94a206631f0352559c691caa9fec278a923a6632c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb62c3f0eb6247fdc2c37b00db5ffcaa943c7a079a6964ae9bc3e6ac877dac41a02b91f71872a6df611de92023fbdc5daf1ee042405823dfaf7701c866e8bcda40603cb832600000168000000b01d9e4300e6ac1d507958bf5052d8c9e1e1bc7a12208d89a56e24ea85d3d3ecdf303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb6303f0eb60bd3f54ff2063fb33f672a45dfadcac51de346608b784fc15b084c7bf3cfb80211c20f3598e8343cfd625bf40611752409d6579bbd2a0db80c11d67f3e7bc9a1000000b004f25c24a1024f4db8f540526ae61b006954d6b66d9b10eeee691cff0e84335a343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6343f0eb6238c5ce68d8e11da3753f0fe793c7440cdaf8b4d523f479c1f2e74891e7ffe7e297a76cc34700663f54f22ac9fa01e9fb9a29c8883f10592d037fe8c692c101d00000168000000b01caac3bb3c8a2174b0e2070b0474c47c19211ba3346208c9b28f450c393479d6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb6383f0eb60ae0760a47e443d776f072009149c55f5547e7f19f4ccee59f72a702593044f910ce8fefeec6386134eba3aeb7ad6fbe413af92cd0fe8cdc507c3105a3dc5698000000b003fedcdef6e05371f07e880d1c82159aa0b97847816f901332d3778573e4c0513c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb63c3f0eb62298dda0e36c15fe6edd38b92ad86edb05142cde6613c6c06398cf0f83e08b752886f7868a4e0a882cd86a67513c1939f1073e1997c584b714a25912ce8c9d1400000168000000b01bb7447592682598e86b4ec5b610bf165085bd34483687edf6f99f929e9506cd403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb6403f0eb609ecf6c49dc247fbae79b9bb42e5bff98cac8982b3214e09e3dd0188be90d1f00fdb10aa44a43c856c74eb6969496a58789f9abde4d30c0094e68b8c093ce38f000000b0030b5d994cbe57962807cfc7ce1e1034d81e19d895440f37773dd20bd9454d48443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb6443f0eb621a55e5b394a1a22a6668073dc7469753c78ce6f79e845e4a8032995e941186c27937840e02c0eac6461b22202d813d4286bdfaaab9a03db590cb39933ed2a0b00000168000000b01ac3c52fe84629bd1ff4968067acb9b087ea5ec55c0b07123b63fa1903f593c4483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb6483f0eb608f9777ef3a04c1fe6030175f481ba93c4112b13c6f5cd2e28475c0f23f15ee70ee791649a8240a9a3fe33241ae564f2b0043c4ef8a78b24d950e6126e9d7086000000b00217de53a29c5bba5f9117827fba0acf0f82bb69a9188e5bbba82c923ea5da3f4c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb64c3f0eb620b1df158f281e46ddefc82e8e10640f73dd70008dbcc508ec6d841c4ea1a563269ff8fb360a12d09beaf9dcb4740e6e5fd0813bbf6e82ff9d770e1f994db70200000168000000b019d045ea3e242de1577dde3b1948b44abf4f00566fdf86367fce549f695620bb503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb6503f0eb60805f839497e50441d8c4930a61db52dfb75cca4daca4c526cb1b6958951ebde0df4121ef06044cddb877adecc815f8ce768dde00c7c0a491dbb4098d3fdfd7d000000b001245f0df87a5fde971a5f3d3156056946e75cfabced0d8000128718a4066736543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb6543f0eb61fbe5fcfe506226b15790fe93fac5ea9ab421191a191442d30d7dea2b402325a25ac79b58be816f4d374419766100908973522ccd3430223e1e168a5feae43f900000168000000b018dcc6a4940232058f0725f5cae4aee4f6b3a1e783b4055ac438af25ceb6adb2583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6583f0eb6071278f39f5c5468551590eb57b9afc832da6e35ee9ecb76b11c111beeb278d50d0092d9463e48f21310c2997e1d5a271ecd7f712050896d62259b1f395e8a74000000b00030dfc84e586402cea3a6f7e2f200037e4bfe8bd0c18ca4447ce19f0966f42d5c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb65c3f0eb61ecae08a3ae4268f4d0257a3f1485943e2a6b322b565c351754239291962bf5124b8fa6fe1c61b190afd895217ac03a2ce99c45de7178148264bc32c640ed0f0380000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018100000000000000000000000000000000000000000000000000000000000001820000000000000000000000000000000000000000000000000000000000000183000000000000000000000000000000000000000000000000000000000000018400000000000000000000000000000000000000000000000000000000000001850000000000000000000000000000000000000000000000000000000000000186000000000000000000000000000000000000000000000000000000000000018700000000000000000000000000000000000000000000000000000000000001880000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000018a000000000000000000000000000000000000000000000000000000000000018b000000000000000000000000000000000000000000000000000000000000018c000000000000000000000000000000000000000000000000000000000000018d000000000000000000000000000000000000000000000000000000000000018e000000000000000000000000000000000000000000000000000000000000018f0000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000019100000000000000000000000000000000000000000000000000000000000001920000000000000000000000000000000000000000000000000000000000000193000000000000000000000000000000000000000000000000000000000000019400000000000000000000000000000000000000000000000000000000000001950000000000000000000000000000000000000000000000000000000000000196000000000000000000000000000000000000000000000000000000000000019700000000000000000000000000000000000000000000000000000000000001980000000000000000000000000000000000000000000000000000000000000199000000000000000000000000000000000000000000000000000000000000019a000000000000000000000000000000000000000000000000000000000000019b000000000000000000000000000000000000000000000000000000000000019c000000000000000000000000000000000000000000000000000000000000019d000000000000000000000000000000000000000000000000000000000000019e000000000000000000000000000000000000000000000000000000000000019f00000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001a100000000000000000000000000000000000000000000000000000000000001a200000000000000000000000000000000000000000000000000000000000001a300000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000000001a500000000000000000000000000000000000000000000000000000000000001a600000000000000000000000000000000000000000000000000000000000001a700000000000000000000000000000000000000000000000000000000000001a800000000000000000000000000000000000000000000000000000000000001a900000000000000000000000000000000000000000000000000000000000001aa00000000000000000000000000000000000000000000000000000000000001ab00000000000000000000000000000000000000000000000000000000000001ac00000000000000000000000000000000000000000000000000000000000001ad00000000000000000000000000000000000000000000000000000000000001ae00000000000000000000000000000000000000000000000000000000000001af00000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b100000000000000000000000000000000000000000000000000000000000001b200000000000000000000000000000000000000000000000000000000000001b300000000000000000000000000000000000000000000000000000000000001b400000000000000000000000000000000000000000000000000000000000001b500000000000000000000000000000000000000000000000000000000000001b600000000000000000000000000000000000000000000000000000000000001b7370000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000028100000000000000000000000000000000000000000000000000000000000002820000000000000000000000000000000000000000000000000000000000000283000000000000000000000000000000000000000000000000000000000000028400000000000000000000000000000000000000000000000000000000000002850000000000000000000000000000000000000000000000000000000000000286000000000000000000000000000000000000000000000000000000000000028700000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000289000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000028b000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028d000000000000000000000000000000000000000000000000000000000000028e000000000000000000000000000000000000000000000000000000000000028f0000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000029100000000000000000000000000000000000000000000000000000000000002920000000000000000000000000000000000000000000000000000000000000293000000000000000000000000000000000000000000000000000000000000029400000000000000000000000000000000000000000000000000000000000002950000000000000000000000000000000000000000000000000000000000000296000000000000000000000000000000000000000000000000000000000000029700000000000000000000000000000000000000000000000000000000000002980000000000000000000000000000000000000000000000000000000000000299000000000000000000000000000000000000000000000000000000000000029a000000000000000000000000000000000000000000000000000000000000029b000000000000000000000000000000000000000000000000000000000000029c000000000000000000000000000000000000000000000000000000000000029d000000000000000000000000000000000000000000000000000000000000029e000000000000000000000000000000000000000000000000000000000000029f00000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a100000000000000000000000000000000000000000000000000000000000002a200000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000002a400000000000000000000000000000000000000000000000000000000000002a500000000000000000000000000000000000000000000000000000000000002a600000000000000000000000000000000000000000000000000000000000002a700000000000000000000000000000000000000000000000000000000000002a800000000000000000000000000000000000000000000000000000000000002a900000000000000000000000000000000000000000000000000000000000002aa00000000000000000000000000000000000000000000000000000000000002ab00000000000000000000000000000000000000000000000000000000000002ac00000000000000000000000000000000000000000000000000000000000002ad00000000000000000000000000000000000000000000000000000000000002ae00000000000000000000000000000000000000000000000000000000000002af00000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002b100000000000000000000000000000000000000000000000000000000000002b200000000000000000000000000000000000000000000000000000000000002b300000000000000000000000000000000000000000000000000000000000002b400000000000000000000000000000000000000000000000000000000000002b500000000000000000000000000000000000000000000000000000000000002b60200000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000381100000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000058a0000000000000000000000000000000000000000000000000000000000000581000000000000000000000000000000000000000000000000000000000000058b0000000000000000000000000000000000000000000000000000000000000582000000000000000000000000000000000000000000000000000000000000058c0000000000000000000000000000000000000000000000000000000000000583000000000000000000000000000000000000000000000000000000000000058d0000000000000000000000000000000000000000000000000000000000000584000000000000000000000000000000000000000000000000000000000000058e0000000000000000000000000000000000000000000000000000000000000585000000000000000000000000000000000000000000000000000000000000058f00000000000000000000000000000000000000000000000000000000000005860000000000000000000000000000000000000000000000000000000000000590000000000000000000000000000000000000000000000000000000000000058700000000000000000000000000000000000000000000000000000000000005910000000000000000000000000000000000000000000000000000000000000588000000000000000000000000000000000000000000000000000000000000059200000000000000000000000000000000000000000000000000000000000005890000000000000000000000000000000000000000000000000000000000000593000000000000000000000000000000000000000000000000000000000000058a0000000000000000000000000000000000000000000000000000000000000594000000000000000000000000000000000000000000000000000000000000058b0000000000000000000000000000000000000000000000000000000000000595000000000000000000000000000000000000000000000000000000000000058c0000000000000000000000000000000000000000000000000000000000000596000000000000000000000000000000000000000000000000000000000000058d0000000000000000000000000000000000000000000000000000000000000597000000000000000000000000000000000000000000000000000000000000058e0000000000000000000000000000000000000000000000000000000000000598000000000000000000000000000000000000000000000000000000000000058f0000000000000000000000000000000000000000000000000000000000000599000011000000021c000000b017e9475ee9e03629c6906db07c80a97f2e1843789788847f08a309ac34173aa9603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6603f0eb6061ef9adf53a588c8c9ed8a60955aa626a3f0fc702734a9af5866ba2541305cc0c0d13939c1c4d164a9a0a542fb954c15632210234250891a68ff5a59ebf176b000000b02fa1aef585680850be7d3469160f52fadde488655e4f7c59ccc931b95ec78125643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb6643f0eb61dd7614490c22ab3848b9f5ea2e453de1a0b54b3c93a4275b9ac93af7ec34c4823c57b2a37a41f3d4286d10cc947fe3d05fe65eefaec006c6ab61db2c96f5de7000000b016f5c8193fbe3a4dfe19b56b2e1ca419657ce509ab5d03a34d0d64329977c7a0683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6683f0eb6052b7a684b185cb0c4282060baf1a4fca1a3b1581647c9bf39f0c628b97392c30b19944df1fa513a8223520ee1554f5b8d96c29347f987b5eafa502c041fa4620000021c000000b02eae2fafdb460c74f6067c23c7ab4d95154929f67223fb7e11338c3fc4280e1c6c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb66c3f0eb61ce3e1fee6a02ed7bc14e71954804e78516ff644dd0ec199fe16ee35e423d93f22d1fbe48d8223617a1018c77ae3f8d73d6307800ec07f90af2078392ecfeade000000b0160248d3959c3e7235a2fd25dfb89eb39ce1869abf3182c79177beb8fed85497703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb6703f0eb60437fb22a0f660d4fbb1681b6c8d9f96d90852e92a1c48e37e5b20af1ed41fba0a26150847d8555eb9ac99c992f149f5c4fb64245bce06da2f64aab269803159000000b02dbab06a312410992d8fc3de7947482f4cadcb8785f87aa2559de6c629889b13743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb6743f0eb61bf062b93c7e32fbf39e2ed4061c491288d497d5f0e340be428148bc4984663621de7c9ee3602785b19960822c7ff37174c7a9112294feb4f38ad2bf943077d50000021c000000b0150ec98deb7a42966d2c44e09154994dd446282bd30601ebd5e2193f6438e18e783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb6783f0eb603447bdcf6d464f9333aafd61e299a31106cf47a3df0c807c2c57b358434acb1093295c29db65982f135e184448d448ffc6005b56fa285fe73cf0538cee0be50000000b02cc73124870214bd65190b992ae342c984126d1899ccf9c69a08414c8ee9280a7c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb67c3f0eb61afce373925c37202b27768eb7b843acc039396704b7bfe286eba342aee4f32d20eafd59393e2ba9e922a83cde1bee0bac2c4aa236697dd937f52d45f99104cc000000b0141b4a48415846baa4b58c9b42f093e80baac9bce6da81101a4c73c5c9996e85803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb6803f0eb60250fc974cb2691d6ac3f790cfc594cb47d1960b51c5472c072fd5bbe99539a8083f167cf3945da728bf293ef6293f2a33c4a74683770522b8395fbf34414b470000021c000000b02bd3b1dedce018e19ca25353dc7f3d63bb770ea9ada178eade729bd2f449b501843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb6843f0eb61a09642de83a3b4462b0be4969543e46f79ddaf8188c3f06cb55fdc9144580241ff77e138f1c2fce20abeff78fb7e8a5e390ec334a3dfcfd7c5f87cc5ef191c3000000b01327cb0297364adedc3ed455f48c8e82430f6b4dfaaf00345eb6ce4c2ef9fb7c883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6883f0eb6015d7d51a2906d41a24d3f4b81618f657f36379c6599c6504b9a30424ef5c69f074b9737497261cb604870f9a7c539c46b2948d7974b8446fca3ba4599a1d83e000000b02ae0329932be1d05d42b9b0e8e1b37fdf2dbb03ac175f80f22dcf65959aa41f88c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb68c3f0eb61915e4e83e183f689a3a06041af038e12f027c892c60be2b0fc0584f79a60d1b1f03fecde4fa33f2583537b24153e3401af58dc45e127c21c0c9e252c4521eba0000021c000000b012344bbced144f0313c81c10a628891c7a740cdf0e837f58a32128d2945a8873903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb6903f0eb60069fe0bf86e7165d9d6870632fd89ffb69ad92d796e457490048ac8b4565396065817f19f5065ef97d1b8b45961345ea28dea68ab20036b410e14cbff026535000000b029ecb353889c212a0bb4e2c93fb732982a4051cbd54a7733674750dfbf0aceef943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6943f0eb6182265a293f6438cd1c34dbecc8c337b66671e1a40353d4f542ab2d5df069a121e107f883ad838168fbe7f6cf2efddda525a2f5571e6fb4605343cd929b2abb1000000b01140cc7742f253274b5163cb57c483b6b1d8ae702257fe7ce78b8358f9bb156a983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb6983f0eb62fdacd392f7e15b3c9b01477661adcf71633630706fc352a1850dae309b6e08e056498abf52e6a13cf5b006f0afd2ef8d9f28bf9bef4828f85786f526462f22c0000021c000000b028f9340dde7a254e433e2a83f1532d3261a4f35ce91ef657abb1ab66246b5be69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb69c3f0eb6172ee65ce9d447b1094c95797e282e159dcbbfab5409bc7398950d5c446727091d1d004290b63c3ac747c727a48bd87489bed0e685bb7a6a499e975f8f1338a8000000b0104d4d3198d0574b82daab8609607e50e93d5001362c7da12bf5dddf5f1ba261a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb6a03f0eb62ee74df3855c19d801395c3217b6d7914d9804981ad0b44e5cbb35696f176d85047119664b0c6e3806e44829bc99299311572d8ad2c901b3c9e2c9d8c9c37f23000000b02805b4c8345829727ac7723ea2ef27cc990994edfcf3757bf01c05ec89cbe8dda43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6a43f0eb6163b67173fb24bd540d5dd342fc428afd530613c67de3b97dcff67e2a9c7b4001c2980fce694405efed10ee25627d30ec1237277998ff98e8e08f1e5f473c59f0000021c000000b00f59cdebeeae5b6fba63f340bafc78eb20a1f1924a00fcc570603865c47c2f58a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb6a83f0eb62df3ceaddb3a1dfc38c2a3ecc952d22b84fca6292ea53372a1258fefd477fa7c037d9a20a0ea725c3e6d8fe46e35242d48bbcf1be69d80d80e4d245f2f240c1a000000b0271235828a362d96b250b9f9548b2266d06e367f10c7f4a034866072ef2c75d4ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb6ac3f0eb61547e7d195904ff9785f24eee160234a0c9502cd7bb2babc2169c2690f2840f71b3601b73c724483365a569d07c3cda8f8881408ad6478b2d2734c6c59d45296000000b00e664ea6448c5f93f1ed3afb6c987385580693235dd57be9b4ca92ec29dcbc4fb03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb6b03f0eb62d004f6831182220704beba77aeeccc5bc6147ba4279b296e58fea7639d88773028a1adaf6c8768075f6d79f1fd11ec7802070acfa71fffc52b77ee5948499110000021c000000b0261eb63ce01431bae9da01b406271d0107d2d810249c73c478f0baf9548d02cbb43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb6b43f0eb61454688beb6e541dafe86ca992fc1de443f9a45e8f8739e065d41cef7488cdee1a428271925048a76de39e57b95fc8432fecb599c138f7d716dda6f2bf34df8d000000b00d72cf609a6a63b8297682b61e346e1f8f6b34b471a9fb0df934ed728f3d4946b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb6b83f0eb62c0cd02286f62644a7d533622c8ac75ff3c5e94b564e31bb29fa44fc9f39146a01969b954ca67aa4ad801f59d16d1961b785123e0e467f209721d96bf9e52608000000b0252b36f735f235df2163496eb7c3179b3f3779a13870f2e8bd5b157fb9ed8fc2bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb6bc3f0eb61360e946414c5841e771b4644498187e7b5e45efa35bb904aa3e7775d9e95ae5194f032be82e4ccba56ce6126afbc2dd6751572ad50d76fb5b48017924956c8400000fa400000168000000b00c7f501af04867dc60ffca70cfd068b9c6cfd645857e7a323d9f47f8f49dd63dc03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb6c03f0eb62b1950dcdcd42a68df5e7b1cde26c1fa2b2a8adc6a22b0df6e649f830499a16100a31c4fa2847ec8e5096714830913fbeee9b3cf221afe44db8c33f25f45b2ff000000b02437b7b18bd03a0358ec9129695f1235769c1b324c45720d01c570061f4e1cb9c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6c43f0eb6126d6a00972a5c661efafc1ef6341318b2c2e780b7303828eea8d1fc3f49e7dc185b83e63e0c50efdcf62dcd1c97bd779eb5f8bbe8e1f61f9fb25bff89f5f97b00000168000000b00b8bd0d546266c009889122b816c6353fe3477d69952f9568209a27f59fe6334c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb6c83f0eb62a25d19732b22e8d16e7c2d78fc2bc94628f2c6d7df73003b2cefa0969fa2e583013eb7cd9942316d4e2f485b62666f34e823da8afa8edfa63d8840cb4a63ff7000000b02344386be1ae3e279075d8e41afb0ccfae00bcc36019f131462fca8c84aea9b0cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb6cc3f0eb61179eabaed08608a568443d9a7d00db2ea278911cb04b74d33132c82a4aa74d3176804a093ea5514147f7587ce33b811d61a9a4cfcb67543e41cb685ef56867200000168000000b00a98518f9c047024d01259e633085dee35991967ad27787ac673fd05bf5ef02bd03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb6d03f0eb629325251889032b14e710a92415eb72e99f3cdfe91cbaf27f739548fcf5abb4f2f206c372f72273b0c6c3c4067c2618d85e6df39c37d6d1ea842de931a06ccee000000b02250b926378c424bc7ff209ecc970769e5655e5473ee70558a9a2512ea0f36a7d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb6d43f0eb610866b7542e664ae8e0d8b94596c084d218c2aa2ded93671777d87090a0b01ca1674855ae9c859384c08bd427fcfb2ac0d7f3bde108af4682887110c54b7136900000168000000b009a4d249f1e27449079ba1a0e4a458886cfdbaf8c0fbf79f0ade578c24bf7d22d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6d83f0eb6283ed30bde6e36d585fa524cf2fab1c8d1586f8fa5a02e4c3ba3af1634bb48462e2cecf185502b5f43f583fb195e5c27bd4b80cad751ec42ecad39197f6759e5000000b0215d39e08d6a466fff8868597e3302041cc9ffe587c2ef79cf047f994f6fc39edc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb6dc3f0eb60f92ec2f98c468d2c596d34f0b0802e758f0cc33f2adb595bbe7e18f6f6b8ec1158106153fa65d5c839204fd316bad4644e3dd6f245f738c6cf16b92ba17a06000000168000000b008b1530447c0786d3f24e95b96405322a4625c89d4d076c34f48b2128a200a19e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6e03f0eb6274b53c6344c3af9bd839a07a496ac6308bd1120b974ad70800e099c9a1bd53d2d396dabdb2e2f837b7ecbb5cafa56c1f4b0225beb266b673117939fe4c7e6dc000000b02069ba9ae3484a943711b0142fcefc9e542ea1769b976e9e136eda1fb4d05095e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb6e43f0eb60e9f6ce9eea26cf6fd201b09bca3fd8190556dc5068234ba00523c15d4cc1bb8148d86cf95846180bb1b4cb7e307a7e07c487f003833f2b0b15bc6191f782d5700000168000000b007bdd3be9d9e7c9176ae311647dc4dbcdbc6fe1ae8a4f5e793b30c98ef809710e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb6e83f0eb62657d4808a2a3f1df50ce1c25632a6fd4021b2b1cd492c94c4786422ff7c62342c45ee66310c33a7b30813707c96515c2c14c3ecfefaea8b7581ee264a2873d3000000b01f763b5539264eb86e9af7cee16af7388b934307af6bedc257d934a61a30dd8cec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb6ec3f0eb60dabeda44480711b34a962c46e3ff81bc7ba0f561a56b3de44bc969c3a2ca8af139a0789eb6265a4f2a4947294a3a27ab3ad20914c0871d4f5c6209f84d8ba4e00000168000000b006ca5478f37c80b5ae3778d0f9784857132b9fabfc79750bd81d671f54e12407f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb6f03f0eb62564553ae00843422c96297d07cea19777865442e11dabb908e2bea964dcef2b2b526f2086ea37cbea915b2b2e324bf66379657e12cf69afb9ec48acaf8900ca000000b01e82bc0f8f0452dca6243f899306f1d2c2f7e498c3406ce69c438f2c7f916a83f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb6f43f0eb60cb86e5e9a5e753f6c32aa7f1fdbf2b5ff1eb0e72e2b33028926f1229f8d35a612a68844414069c92a2ddc2d463f9d14eb11c2225fdcf0f93a307b25ea39474500000168000000b005d6d533495a84d9e5c0c08bab1442f14a90413d104df4301c87c1a5ba41b0fef83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb6f83f0eb62470d5f535e64766641f7137b96a9c31aeeaf5d3f4f22add4d4d192fca3d7c222a5eefdadcc83bf0221aa2e5dfce46909ade070f26a3e8d3fe56a33314e98dc1000000b01d8f3cc9e4e25700ddad874444a2ec6cfa5c8629d714ec0ae0ade9b2e4f1f77afc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb6fc3f0eb60bc4ef18f03c7963a3bbf239d177ed503683527841ffb226cd914ba904edc29d11b308fe971e6ded61b723e7f7db97af227663b373b1701d7e9ad5ac4f99d43c00000168000000b0054ae0036f4113f1cc6530d57ce7f80d18546677e12a139f912e7d6294cb4b2600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb600400eb6237d56af8bc44b8a9ba8b8f26b0696cbe64f976508c6aa0191b773b62f9e0919296b709532a6401459a3eaa0916a412ad242a8a03a7867f842c0fdb97a4a1ab8000000b01d03479a0ac8e618c451f78e1676a188c820ab64a7f10b7a5554a56fbf7b91a204400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb604400eb60b38f9e91623087b8a606283a34ba26c044777b312dbd19642380765df775cc5112713cebd04fd05485b9431c9af4ccaf03a88ee448d8f8cf34191692a236e6400000168000000b0045760bdc51f181603ee78902e83f2a74fb90808f4fe92c3d598d7e8fa2bd81d08400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb608400eb622f1617fb1aadaa2824d293c3cda4be7b413bc9fd9a2c971065e2f730a27a34128df7b65588ccf2c40485aea633df646a006cddb0b548767b767b97654d3b4e0000000b01c0fc85460a6ea3cfbdb3f48c8129c22ff854cf5bbc58a9e99befff624dc1e990c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60c400eb60a457aa36c010c9fc1e9aa3e54e79d063bac194426b050ba86a261ec44d7e9bc1033948912e301297fe4dbec7b4b4765279f2a7f58620eb137abebef8f83fb5b00000168000000b00363e1781afd1c3a3b77c04ae01fed41871da99a08d311e81a03326f5f8c651410400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb610400eb621fde23a0788dec6b9d670f6ee764681eb785e30ed7748954ac889f96f88303827ebfc1fae6ad35077d1a2a514d9f0e0d76b6f6c1f29068bfbd213fcba3441d7000000b01b1c490eb684ee613364870379ae96bd36e9ee86cf9a09c2de295a7c8a3cab9014400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb614400eb60951fb5dc1df10c3f972f1f9068397a07310bad53a84cfdecb0cbc72aa3876b30f40154368c1054db76e23a72ce741ff5f03cc106c368dd57c164675f4e488523800000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000001c100000000000000000000000000000000000000000000000000000000000001c200000000000000000000000000000000000000000000000000000000000001c300000000000000000000000000000000000000000000000000000000000001c400000000000000000000000000000000000000000000000000000000000001c500000000000000000000000000000000000000000000000000000000000001c600000000000000000000000000000000000000000000000000000000000001c700000000000000000000000000000000000000000000000000000000000001c800000000000000000000000000000000000000000000000000000000000001c900000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000001cb00000000000000000000000000000000000000000000000000000000000001cc00000000000000000000000000000000000000000000000000000000000001cd00000000000000000000000000000000000000000000000000000000000001ce00000000000000000000000000000000000000000000000000000000000001cf00000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001d100000000000000000000000000000000000000000000000000000000000001d200000000000000000000000000000000000000000000000000000000000001d300000000000000000000000000000000000000000000000000000000000001d400000000000000000000000000000000000000000000000000000000000001d500000000000000000000000000000000000000000000000000000000000001d600000000000000000000000000000000000000000000000000000000000001d700000000000000000000000000000000000000000000000000000000000001d800000000000000000000000000000000000000000000000000000000000001d900000000000000000000000000000000000000000000000000000000000001da00000000000000000000000000000000000000000000000000000000000001db00000000000000000000000000000000000000000000000000000000000001dc00000000000000000000000000000000000000000000000000000000000001dd00000000000000000000000000000000000000000000000000000000000001de00000000000000000000000000000000000000000000000000000000000001df00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001e100000000000000000000000000000000000000000000000000000000000001e200000000000000000000000000000000000000000000000000000000000001e300000000000000000000000000000000000000000000000000000000000001e400000000000000000000000000000000000000000000000000000000000001e500000000000000000000000000000000000000000000000000000000000001e600000000000000000000000000000000000000000000000000000000000001e700000000000000000000000000000000000000000000000000000000000001e800000000000000000000000000000000000000000000000000000000000001e900000000000000000000000000000000000000000000000000000000000001ea00000000000000000000000000000000000000000000000000000000000001eb00000000000000000000000000000000000000000000000000000000000001ec00000000000000000000000000000000000000000000000000000000000001ed00000000000000000000000000000000000000000000000000000000000001ee00000000000000000000000000000000000000000000000000000000000001ef00000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000001f100000000000000000000000000000000000000000000000000000000000001f200000000000000000000000000000000000000000000000000000000000001f300000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000001f600000000000000000000000000000000000000000000000000000000000001f73700000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002c100000000000000000000000000000000000000000000000000000000000002c200000000000000000000000000000000000000000000000000000000000002c300000000000000000000000000000000000000000000000000000000000002c400000000000000000000000000000000000000000000000000000000000002c500000000000000000000000000000000000000000000000000000000000002c600000000000000000000000000000000000000000000000000000000000002c700000000000000000000000000000000000000000000000000000000000002c800000000000000000000000000000000000000000000000000000000000002c900000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000002cb00000000000000000000000000000000000000000000000000000000000002cc00000000000000000000000000000000000000000000000000000000000002cd00000000000000000000000000000000000000000000000000000000000002ce00000000000000000000000000000000000000000000000000000000000002cf00000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002d100000000000000000000000000000000000000000000000000000000000002d200000000000000000000000000000000000000000000000000000000000002d300000000000000000000000000000000000000000000000000000000000002d400000000000000000000000000000000000000000000000000000000000002d500000000000000000000000000000000000000000000000000000000000002d600000000000000000000000000000000000000000000000000000000000002d700000000000000000000000000000000000000000000000000000000000002d800000000000000000000000000000000000000000000000000000000000002d900000000000000000000000000000000000000000000000000000000000002da00000000000000000000000000000000000000000000000000000000000002db00000000000000000000000000000000000000000000000000000000000002dc00000000000000000000000000000000000000000000000000000000000002dd00000000000000000000000000000000000000000000000000000000000002de00000000000000000000000000000000000000000000000000000000000002df00000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002e100000000000000000000000000000000000000000000000000000000000002e200000000000000000000000000000000000000000000000000000000000002e300000000000000000000000000000000000000000000000000000000000002e400000000000000000000000000000000000000000000000000000000000002e500000000000000000000000000000000000000000000000000000000000002e600000000000000000000000000000000000000000000000000000000000002e700000000000000000000000000000000000000000000000000000000000002e800000000000000000000000000000000000000000000000000000000000002e900000000000000000000000000000000000000000000000000000000000002ea00000000000000000000000000000000000000000000000000000000000002eb00000000000000000000000000000000000000000000000000000000000002ec00000000000000000000000000000000000000000000000000000000000002ed00000000000000000000000000000000000000000000000000000000000002ee00000000000000000000000000000000000000000000000000000000000002ef00000000000000000000000000000000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000002f100000000000000000000000000000000000000000000000000000000000002f200000000000000000000000000000000000000000000000000000000000002f300000000000000000000000000000000000000000000000000000000000002f400000000000000000000000000000000000000000000000000000000000002f500000000000000000000000000000000000000000000000000000000000002f60200000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c11000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000005ca00000000000000000000000000000000000000000000000000000000000005c100000000000000000000000000000000000000000000000000000000000005cb00000000000000000000000000000000000000000000000000000000000005c200000000000000000000000000000000000000000000000000000000000005cc00000000000000000000000000000000000000000000000000000000000005c300000000000000000000000000000000000000000000000000000000000005cd00000000000000000000000000000000000000000000000000000000000005c400000000000000000000000000000000000000000000000000000000000005ce00000000000000000000000000000000000000000000000000000000000005c500000000000000000000000000000000000000000000000000000000000005cf00000000000000000000000000000000000000000000000000000000000005c600000000000000000000000000000000000000000000000000000000000005d000000000000000000000000000000000000000000000000000000000000005c700000000000000000000000000000000000000000000000000000000000005d100000000000000000000000000000000000000000000000000000000000005c800000000000000000000000000000000000000000000000000000000000005d200000000000000000000000000000000000000000000000000000000000005c900000000000000000000000000000000000000000000000000000000000005d300000000000000000000000000000000000000000000000000000000000005ca00000000000000000000000000000000000000000000000000000000000005d400000000000000000000000000000000000000000000000000000000000005cb00000000000000000000000000000000000000000000000000000000000005d500000000000000000000000000000000000000000000000000000000000005cc00000000000000000000000000000000000000000000000000000000000005d600000000000000000000000000000000000000000000000000000000000005cd00000000000000000000000000000000000000000000000000000000000005d700000000000000000000000000000000000000000000000000000000000005ce00000000000000000000000000000000000000000000000000000000000005d800000000000000000000000000000000000000000000000000000000000005cf00000000000000000000000000000000000000000000000000000000000005d9000011000000021c000000b00270623270db205e7301080591bbe7dbbe824b2b1ca7910c5e6d8cf5c4ecf20b18400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb618400eb6210a62f45d66e2eaf15fb8b1a012411c22dcffc2014bc7b98f32e47fd4e8bd2f26f87cda0448d774af5aea5fc675eb7b0ed010fd32fd85b0403c6e831f94cece000000b01a28c9c90c62f2856aedcebe2b4a91576e4e9017e36e88e72293b502ef9d38871c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb61c400eb6085e7c1817bd14e830fc39b3b81f923aaa755c664e594f030f7716f90f9903aa0e4c95fdbe9f0971eef76b61de833c9996686da1800b0cf9c080a0fc5a451549000000b0017ce2ecc6b92482aa8a4fc04357e275f5e6ecbc307c1030a2d7e77c2a4d7f0220400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb620400eb62016e3aeb344e70f28e9006c51ae3bb65a41a153152046ddd39d3f063a494a262604fd945a26db98e6e4321a7811e6154634b28e46d204d484a6c90984f55bc50000021c000000b019354a836240f6a9a2771678dce68bf1a5b331a8f743080b66fe0f8954fdc57e24400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb624400eb6076afcd26d9b190c6885816e69bb8cd4e1d9fdf7622dce2753e1717f74f990a10d5916b8147d0d962680b31c901f3733cdcd0f3293df8c1e04eafb82bfa5a240000000b0008963a71c9728a6e213977af4f3dd102d4b8e4d44508f54e74242028fae0bf928400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb628400eb61f2364690922eb3360724827034a365091a642e428f4c6021807998c9fa9d71d25117e4eb004dfbd1e6d79d529ade0af7d99541f5aa683f8c911238fea55e8bc000000b01841cb3db81efacdda005e338e82868bdd17d33a0b17872fab686a0fba5e52752c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb62c400eb606777d8cc3791d30a00ec9291b57876f193e9f8876024d4b984bcc05da5a1d980c6597726a5b11ba5e09fad741bb31ce0531b0c3a7b40b424955560925062f370000021c000000b02ffa32d453a6ccf4d1ed24ec281130078ce41826d1de7f0a6f8e921ce50e98f130400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb630400eb61e2fe5235f00ef5797fb8fe1b4e630eac90ae4753cc945265c71f413050a6414241dff0905e2e3e155f6c18fdb49db49b4fdf5b06e7b031d0d7b7e164fb675b3000000b0174e4bf80dfcfef21189a5ee401e8126147c74cb1eec0653efd2c4961fbedf6c34400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb634400eb60583fe4719572154d79810e3ccf3820950a3411989d6cc6fdcb6268c3fbaaa8f0b72182cc03915de95934291f3572c683c965254bb888a668dbfb08f8a66bc2e000000b02f06b38ea984d11909766ca6d9ad2aa1c448b9b7e5b2fe2eb3f8eca34a6f25e838400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb638400eb61d3c65ddb4def37bcf84d79c66822b85006f8606509dc44aa0dc4e996a6af10b232a7fc35bc0e8058d80094a8ce5d5e3ec629741824f824151e5d89cb51702aa0000021c000000b0165accb263db03164912eda8f1ba7bc04be1165c32c08578343d1f1c851f6c633c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb63c400eb604907f016f3525790f21589e7e8f7ca38807e2aa9dab4b9421208112a51b37860a7e98e716171a02cd1c8a4ca4f3270273faf3e5cf5d098ad22a0b15efc74925000000b02e133448ff62d53d40ffb4618b49253bfbad5b48f9877d52f8634729afcfb2df40400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb640400eb61c48e6980abcf7a0070e1f57181e261f37d427976472436ee546a91fcfcb7e022237007db19eec29c50951053e81d07e23c738d296240165965033231a778fa1000000b015674d6cb9b9073a809c3563a356765a8345b7ed4695049c78a779a2ea7ff95a44400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb644400eb6039cffbbc513299d46aaa059302b773dbf6c843bb17fcab8658adb990a7bc47d098b19a16bf51e2704a5d207568f219cab5f9576e33188af1694659c5527d61c0000021c000000b02d1fb5035540d9617888fc1c3ce51fd63311fcda0d5bfc773ccda1b015303fd648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb648400eb61b556752609afbc43e976711c9ba20b96f38c9287846c29329b103a6352c0af921438138077cf04dfc9298bff01dcb185b2bda63a9f88089daba8da97fd81c98000000b01473ce270f970b5eb8257d1e54f270f4baaa597e5a6983c0bd11d4294fe086514c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb64c400eb602a980761af12dc17e33e813e1c771d7f6d125ccc55449dca9f5361f6fdc517408979a5bc1d3224b3c2f19c2082b1c36e2c43707f70607d35afec022ba886313000000b02c2c35bdab1edd85b01243d6ee811a706a769e6b21307b9b8137fc367a90cccd50400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb650400eb61a61e80cb678ffe87620aecc7b561b53a69d6ab98c1b41b76e1b5e2c9a8c97f0205001f25d5af472341be07aa1b9c5b292907bf4bdccffae1f24e82fe538a98f0000021c000000b013804ee165750f82efaec4d9068e6b8ef20efb0f6e3e02e5017c2eafb541134854400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb654400eb601b6013070cf31e5b5bd2fce93636c722e35c75dd928c900ee5f90a5d53cde6b07a41b1617b1266f73b8617cb9c716d11a28d8990ada86f79f691aa91fe8f00a000000b02b38b67800fce1a9e79b8b91a01d150aa1db3ffc3504fabfc5a256bcdff159c458400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb658400eb6196e68c70c57040cada9f6872cf215edde020c4a9fefc0dbb285b8b2ffed24e71f5c82acb338f8966ba528355355c04cc9f51d85d1a17ed2638f42b64a993686000000b0128ccf9bbb5313a727380c93b82a662929739ca08212820945e689361aa1a03f5c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb65c400eb600c281eac6ad3609ed46778944ff670c659a68eeecfd482532c9eb2c3a9d6b6206b09bd06d8f2a93ab41a9376b63116b518d7a2a1eaf061be3d3752f85497d010000021c000000b02a45373256dae5ce1f24d34c51b90fa4d93fe18d48d979e40a0cb1434551e6bb60400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb660400eb6187ae98162350830e5333e41de8e10881566addbb3c43ffff6f01339654db1de1e6903670916fcbaa32e6ff004f1bae70159bf16e575fdf6a7f99d3caff9c37d000000b011995056113117cb5ec1544e69c660c360d83e3195e7012d8a50e3bc80022d3664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb664400eb630335117fdbcda57dd2004fa781cba03c532f2c87a8b37dabb163b468ffdf85a05bd1c8ac36d2eb7e2caf0f21cff0c0588f21bbb32838540283dcfb5eaaa09f8000000b02951b7ecacb8e9f256ae1b0703550a3f10a4831e5cadf9084e770bc9aab273b268400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb668400eb617876a3bb8130c551cbc85fc902a0b224ccb4f6cc798bf243b5a6dbfcaae3ed51d7584215ef500dedab7b7aab68db58138be60a7f94a7d1aec63f7c3155a50740000021c000000b010a5d110670f1bef964a9c091b625b5d983cdfc2a9bb8051cebb3e42e562ba2d6c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb66c400eb62f3fd1d2539ade7c14a94cb529b8b49dfc9794598e5fb6feff8095ccf55e855104c99d45194b32dc1a5438acce9b069fc056bd4c465804646ca82a3c500a96ef000000b0285e38a70296ee168e3762c1b4f104d9480924af7082782c92e16650101300a970400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb670400eb61693eaf60df110795445cdb741c605bc842ff0fddb6d3e487fc4c846300ecbcc1c8204dbb4d305031240ff656829b01b702302390d1efc3f30ce52497abadd6b000000b00fb251cabced2013cdd3e3c3ccfe55f7cfa18153bd8fff76132598c94ac3472474400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb674400eb62e4c528ca978e2a04c32946fdb54af3833fc35eaa234362343eaf0535abf124803d61dff6f29370051dd806780370139f7bb5edd5a2c8388b11284c2b56b23e600000fa400000168000000b0276ab9615874f23ac5c0aa7c668cff737f6dc6408456f750d74bc0d675738da078400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb678400eb615a06bb063cf149d8bcf1571f3620056bb94928eef41bd6cc42f22cc956f58c31b8e85960ab1092749ca472019c5aab5a787a3ca20f37b637538accfe01b6a62000000b00ebed28512cb2438055d2b7e7e9a5092070622e4d1647e9a578ff34fb023d41b7c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb67c400eb62d58d346ff56e6c483bbdc2a8cf0a9d26b60d77bb608b54788554ad9c01f9f3f02e29eb9c5073b248966c82231d2fbd42f20006e6e0102acf57cdf491acbb0dd00000168000000b026773a1bae52f65efd49f2371828fa0db6d267d1982b76751bb61b5cdad41a9780400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb680400eb614acec6ab9ad18c1c3585d2ca4fdfaf0f2f9342003163c9108997d52facfe5ba1a9b0650608f0d4b81538edacb61a54fdeec455b34c7fa87b9a30756457bf759000000b00dcb533f68a9285c3ce6733930364b2c3e6ac475e538fdbe9bfa4dd61584611284400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb684400eb62c6554015534eae8bb4523e53e8ca46ca2c5790cc9dd346bccbfa56025802c3601ef1f741ae53f48c0f00fdce36ef66e6684a1ff81d581d139e739cf802c3dd400000168000000b02583bad60430fa8334d339f1c9c4f4a7ee370962abfff599602075e34034a78e88400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb688400eb613b96d250f8b1ce5fae1a4e75699f58b2a5dd5b116eabbb54d03d7d9603072b119a7870ab66d116fb8dcd6957cfd9fea1650e6ec489c79abfe0d61dcaadc8450000000b00cd7d3f9be872c80746fbaf3e1d245c675cf6606f90d7ce2e064a85c7ae4ee098c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb68c400eb62b71d4bbab12ef0cf2ce6b9ff0289f06da2a1a9dddb1b3901129ffe68ae0b92d00fba02e70c3436cf8795797950af1089de9439095aa00f57e519455e58ccacb00000168000000b024903b905a0efea76c5c81ac7b60ef42259baaf3bfd474bda48ad069a595348590400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb690400eb612c5eddf6569210a326aeca20835f02561c277422abf3ad9916e325fc590ffa818b407c50c4b1593f0661e502e999a844db5887d5c70f8d04277bc63103d1147000000b00be454b4146530a4abf902ae936e4060ad3407980ce1fc0724cf02e2e0457b0094400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb694400eb62a7e557600f0f3312a57b35aa1c499a1118ebc2ef18632b455945a6cf0414624000820e8c6a1479130029f5246a6eba2d54de521a97e8019c2bbeedc4aed57c200000168000000b0239cbc4aafed02cba3e5c9672cfce9dc5d004c84d3a8f3e1e8f52af00af5c17c98400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb698400eb611d26e99bb47252e69f4345cb9d1eabf992718d33e93b9fdd5d88ce62af18c9f17c0887f622919b827ef660ae035951e851a2a0e704577f486e216e9759d9e3e000000b00af0d56e6a4334c8e3824a69450a3afae498a92920b67b2b69395d6945a607f79c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb69c400eb6298ad63056cef75561e0fb155360943b48f35dc0055ab1d899feb4f355a1d31b2f78f015fdb0ebdf1fdc2cc379c43e9a34e66efb370c6fcf4b083ef6a04de4ba00000168000000b022a93d0505cb06efdb6f1121de98e4769464ee15e77d73062d5f857670564e73a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb6a0400eb610deef5411252952a17d7c176b6de559d08bba64526839221a42e76c9052199616cd0939b8071ddc5f78adc591d18fb8bc7ecb9f8419f718cb4c716fdafe2b35000000b009fd5628c02138ed1b0b9223f6a635951bfd4aba348afa4fada3b7efab0694eea4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6a4400eb6289756eaacacfb79996a42d004fc8ed58057ff51192f30fcde690f79bb0260122e8570d0538ef0035765747e2b6039346c4b108c4ae0eef38f72997d05ae71b100000168000000b021b5bdbf5ba90b1412f858dc9034df10cbc98fa6fb51f22a71c9dffcd5b6db6aa8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb6a8400eb60feb700e67032d76d906c3d21d09dff407f05bf5663cb8465ead41f2f5b2a68d15d989f40de522009701f580436d8a52f3e36d3097ee763d0fb6cbf6405eb82c000000b00909d6e315ff3d115294d9dea842302f5361ec4b485f7973f20e1276106721e5ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb6ac400eb627a3d7a5028aff9dd0f38a8ab698896fb7bca0e22d03b02122d36a002062ed092d91f18aa96cf4278eeebc38dcfc33cea3afb21d5eb56e17d3dcf4036b0efea800000168000000b020c23e79b1870f384a81a09741d0d9ab032e31380f26714eb6343a833b176861b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb6b0400eb60ef7f0c8bce1319b10900b8ccea5da8e3f54fd867a11376aa3179c795b13338414e60aae63c32624ce8b3d3af50984ed2b480ec1abc2f5615421267ca5bf4523000000b00816579d6bdd41358a1e219959de2ac98ac68ddc5c33f89836786cfc75c7aedcb4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb6b4400eb626b0585f586903c2087cd24568348409ef21427340d82f45673dc48685c37a002c9e7244ff4af84bc67803f38e982e68db1453ae7289ed3c18474e89d06f8b9f00000168000000b01fcebf340765135c820ae851f36cd4453a92d2c922faf072fa9e9509a077f558b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb6b8400eb60e04718312bf35bf481953478041d52876b99f178de5b68ee781f6ffc073c07b13f28b68b9a12a49061484f5a6a57f8762acb052bf977485988b81030b1fd21a000000b00722d857c1bb4559c1a769540b7a2563c22b2f6d700877bc7ae2c782db283bd3bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb6bc400eb625bcd919ae4707e640061a0019d07ea42685e40454acae69aba81f0ceb2406f72baaf2ff5528fc6ffe014bae403429031278f53f865e6c605cb1a91035d0189600000168000000b01edb3fee5d431780b994300ca508cedf71f7745a36cf6f973f08ef9005d8824fc0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb6c0400eb60d10f23d689d39e37fa29b0231ddcfc2ae1e40a8a1ba35b32bec518625d44d7212ff0c230f7f2e6d3d9dccb058417a219a1151e3d36bf3a9dcf5db8970805f11000000b0062f59121799497df930b10ebd161ffdf98fd0fe83dcf6e0bf4d22094088c8cac4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb6c4400eb624c959d404250c0a778f61bacb6c793e5dea859568812d8df0127993508493ee2ab773b9ab070094358a9368f1d0239d49dd96d09a32eb84a11c03969b30a58d00000168000000b01de7c0a8b3211ba4f11d77c756a4c979a95c15eb4aa3eebb83734a166b390f46c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb6c8400eb60c1d72f7be7b3e07b72be2bce379ca5ce582e239b58eb4d77056ac0c8b34da69120b8cdd655d32917527146b09dd74bbd175f374e74072ce2160360fd5e0ec08000000b0053bd9cc6d774da230b9f8c96eb21a9830f4728f97b1760503b77c8fa5e955c1cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb6cc400eb623d5da8e5a03102eaf18a9757d0873d8954f27267c55acb2347cd419b5e520e529c3f47400e504b86d13db23a36c1e3781423861ae076aa8e5865e1d00913284380000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020100000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000000000000203000000000000000000000000000000000000000000000000000000000000020400000000000000000000000000000000000000000000000000000000000002050000000000000000000000000000000000000000000000000000000000000206000000000000000000000000000000000000000000000000000000000000020700000000000000000000000000000000000000000000000000000000000002080000000000000000000000000000000000000000000000000000000000000209000000000000000000000000000000000000000000000000000000000000020a000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000020d000000000000000000000000000000000000000000000000000000000000020e000000000000000000000000000000000000000000000000000000000000020f0000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000021100000000000000000000000000000000000000000000000000000000000002120000000000000000000000000000000000000000000000000000000000000213000000000000000000000000000000000000000000000000000000000000021400000000000000000000000000000000000000000000000000000000000002150000000000000000000000000000000000000000000000000000000000000216000000000000000000000000000000000000000000000000000000000000021700000000000000000000000000000000000000000000000000000000000002180000000000000000000000000000000000000000000000000000000000000219000000000000000000000000000000000000000000000000000000000000021a000000000000000000000000000000000000000000000000000000000000021b000000000000000000000000000000000000000000000000000000000000021c000000000000000000000000000000000000000000000000000000000000021d000000000000000000000000000000000000000000000000000000000000021e000000000000000000000000000000000000000000000000000000000000021f0000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000022100000000000000000000000000000000000000000000000000000000000002220000000000000000000000000000000000000000000000000000000000000223000000000000000000000000000000000000000000000000000000000000022400000000000000000000000000000000000000000000000000000000000002250000000000000000000000000000000000000000000000000000000000000226000000000000000000000000000000000000000000000000000000000000022700000000000000000000000000000000000000000000000000000000000002280000000000000000000000000000000000000000000000000000000000000229000000000000000000000000000000000000000000000000000000000000022a000000000000000000000000000000000000000000000000000000000000022b000000000000000000000000000000000000000000000000000000000000022c000000000000000000000000000000000000000000000000000000000000022d000000000000000000000000000000000000000000000000000000000000022e000000000000000000000000000000000000000000000000000000000000022f00000000000000000000000000000000000000000000000000000000000002300000000000000000000000000000000000000000000000000000000000000231000000000000000000000000000000000000000000000000000000000000023200000000000000000000000000000000000000000000000000000000000002330000000000000000000000000000000000000000000000000000000000000234000000000000000000000000000000000000000000000000000000000000023500000000000000000000000000000000000000000000000000000000000002360000000000000000000000000000000000000000000000000000000000000237370000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030100000000000000000000000000000000000000000000000000000000000003020000000000000000000000000000000000000000000000000000000000000303000000000000000000000000000000000000000000000000000000000000030400000000000000000000000000000000000000000000000000000000000003050000000000000000000000000000000000000000000000000000000000000306000000000000000000000000000000000000000000000000000000000000030700000000000000000000000000000000000000000000000000000000000003080000000000000000000000000000000000000000000000000000000000000309000000000000000000000000000000000000000000000000000000000000030a000000000000000000000000000000000000000000000000000000000000030b000000000000000000000000000000000000000000000000000000000000030c000000000000000000000000000000000000000000000000000000000000030d000000000000000000000000000000000000000000000000000000000000030e000000000000000000000000000000000000000000000000000000000000030f0000000000000000000000000000000000000000000000000000000000000310000000000000000000000000000000000000000000000000000000000000031100000000000000000000000000000000000000000000000000000000000003120000000000000000000000000000000000000000000000000000000000000313000000000000000000000000000000000000000000000000000000000000031400000000000000000000000000000000000000000000000000000000000003150000000000000000000000000000000000000000000000000000000000000316000000000000000000000000000000000000000000000000000000000000031700000000000000000000000000000000000000000000000000000000000003180000000000000000000000000000000000000000000000000000000000000319000000000000000000000000000000000000000000000000000000000000031a000000000000000000000000000000000000000000000000000000000000031b000000000000000000000000000000000000000000000000000000000000031c000000000000000000000000000000000000000000000000000000000000031d000000000000000000000000000000000000000000000000000000000000031e000000000000000000000000000000000000000000000000000000000000031f0000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032100000000000000000000000000000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000323000000000000000000000000000000000000000000000000000000000000032400000000000000000000000000000000000000000000000000000000000003250000000000000000000000000000000000000000000000000000000000000326000000000000000000000000000000000000000000000000000000000000032700000000000000000000000000000000000000000000000000000000000003280000000000000000000000000000000000000000000000000000000000000329000000000000000000000000000000000000000000000000000000000000032a000000000000000000000000000000000000000000000000000000000000032b000000000000000000000000000000000000000000000000000000000000032c000000000000000000000000000000000000000000000000000000000000032d000000000000000000000000000000000000000000000000000000000000032e000000000000000000000000000000000000000000000000000000000000032f00000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000331000000000000000000000000000000000000000000000000000000000000033200000000000000000000000000000000000000000000000000000000000003330000000000000000000000000000000000000000000000000000000000000334000000000000000000000000000000000000000000000000000000000000033500000000000000000000000000000000000000000000000000000000000003360200000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000401100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060a0000000000000000000000000000000000000000000000000000000000000601000000000000000000000000000000000000000000000000000000000000060b0000000000000000000000000000000000000000000000000000000000000602000000000000000000000000000000000000000000000000000000000000060c0000000000000000000000000000000000000000000000000000000000000603000000000000000000000000000000000000000000000000000000000000060d0000000000000000000000000000000000000000000000000000000000000604000000000000000000000000000000000000000000000000000000000000060e0000000000000000000000000000000000000000000000000000000000000605000000000000000000000000000000000000000000000000000000000000060f00000000000000000000000000000000000000000000000000000000000006060000000000000000000000000000000000000000000000000000000000000610000000000000000000000000000000000000000000000000000000000000060700000000000000000000000000000000000000000000000000000000000006110000000000000000000000000000000000000000000000000000000000000608000000000000000000000000000000000000000000000000000000000000061200000000000000000000000000000000000000000000000000000000000006090000000000000000000000000000000000000000000000000000000000000613000000000000000000000000000000000000000000000000000000000000060a0000000000000000000000000000000000000000000000000000000000000614000000000000000000000000000000000000000000000000000000000000060b0000000000000000000000000000000000000000000000000000000000000615000000000000000000000000000000000000000000000000000000000000060c0000000000000000000000000000000000000000000000000000000000000616000000000000000000000000000000000000000000000000000000000000060d0000000000000000000000000000000000000000000000000000000000000617000000000000000000000000000000000000000000000000000000000000060e0000000000000000000000000000000000000000000000000000000000000618000000000000000000000000000000000000000000000000000000000000060f0000000000000000000000000000000000000000000000000000000000000619000011000000021c000000b01cf4416308ff1fc928a6bf820840c413e0c0b77c5e786ddfc7dda49cd0999c3dd0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb6d0400eb60b29f3b21459422beeb52a779515c4f71ce783cac96333fbb4c10692f095676011180d97bb3b36b5acb05c25bb796f5608da9505fb14f1f265ca90963b4178ff000000b004485a86c35551c668434084204e153268591420ab85f5294821d7160b49e2b8d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb6d4400eb622e25b48afe11452e6a1f1302ea46e72ccb3c8b7902a2bd678e72ea01b45addc28d0752e56c308dca49d22de550818d1b8a6d9f2c1dbe9cd29f0b8a365f1bf7b000000b01c00c21d5edd23ed6030073cb9dcbeae1825590d724ced040c47ff2335fa2934d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb6d8400eb60a36746c6a374650263e723246b1bf91544c255bdd37b31ff92b611955f5f45710248e5211193ad9e439a3e06d1569f0403f36970ee97116aa34eb1ca0a205f60000021c000000b00354db41193355ea9fcc883ed1ea0fcc9fbdb5b1bf5a744d8c8c319c70aa6fafdc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb6dc400eb621eedc0305bf18771e2b38eae040690d04186a48a3feaafabd51892680a63ad327dcf5e8aca10d00dc266a9906a4136bf00b7b83d5b068f16e5b1329cb524c72000000b01b0d42d7b4bb281197b94ef76b78b9484f89fa9e86216c2850b259a99b5ab62be0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb6e0400eb60942f526c0154a745dc7b9ecf84dba2b8bb0c6ecf10c32443d95bb9fbb56814e0f310f0c66f73efe1bc2eb9b1eb1648a77a3d82822bdf03aee9f45a3060292ed000000b002615bfb6f115a0ed755cff983860a66d7225742d32ef371d0f68c22d60afca6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb6e4400eb620fb5cbd5b9d1c9b55b480a591dc63a73b7d0bd9b7d32a1f01bbe3ace606c7ca26e976a3027f112513afb253b8400e0627701d14e984e815b2c56db030b2d9690000021c000000b01a19c3920a992c35cf4296b21d14b3e286ee9c2f99f5eb4c951cb43000bb4322e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6e8400eb6084f75e115f34e98955101a7a9e9b4c5c315687e04e0b1688200162620b70e450e3d8fc6bcd54322534c3355d04d5f24af0879b936926f5f3309a0296b631fe4000000b0016ddcb5c4ef5e330edf17b4352205010e86f8d3e70372961560e6a93b6b899dec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb6ec400eb62007dd77b17b20bf8d3dc86043785e4172e1ad6acba7a94346263e334b6754c125f5f75d585d15494b38fa0e69dc08a05ed4bea5fd596739f72fc83696136660000000b01926444c6077305a06cbde6cceb0ae7cbe533dc0adca6a70d9870eb6661bd019f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6f0400eb6075bf69b6bd152bcccda49625b85af5ffa7a0a0f18b5308cc66a70ac86179b3c0d4a108112b347468ad57b1081e959bee66d1b4a4a66ee837773faafd0c3acdb0000021c000000b0007a5d701acd625746685f6ee6bdff9b45eb9a64fad7f1ba59cb412fa0cc1694f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb6f4400eb61f145e32075924e3c4c7101af51458dbaa464efbdf7c28678a9098b9b0c7e1b825027817ae3b196d82c241c91b78033a96396037112de65e3b9a22bcfb73f357000000b01832c506b655347e3e552627804ca916f5b7df51c19ee9951df1693ccb7c5d10f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb6f8400eb606687755c1af56e10463911d0d21a9fa31deaba02c89afb10ad4cb32eb7828330c56913b68914b6ac25ec2cb338554591dd1bcdb5e3b6da7bbde5536362439d2000000b02feb2c9d51dd06a53641ece019db5292a584243e8865e16fe2179149f62ca38cfc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb6fc400eb61e20deec5d372907fc5057d5a6b05375e1aaf08cf350a78bcefaf34016286eaf240ef8d204191d91ba4b8983cd13fdd4cd9e01c82502658280047d4360d4804e0000021c000000b017a6cfd6dc3bc39624f9967152205e32c37c048c927b0904929824f9a605f73800410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb600410eb60574f810178d5b053becd8d7bebda49469434d31405e2ed54f3f25b950d8b52a0b6311f5be6f4f8ef9e80a85e5214ef355365e6c720feccc0048afbc9b84c6c9000000b02f5f376d77c395bd1ce65d29ebaf07ae73484979594200df56be4d06d0b63db404410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb604410eb61d94e9bc831db81fe2f4c81f78840891af6f15c7c42cc6fb43a1aefcf0b208d7238303a229ffaca9a0eff9cd9ee7b2f09b622702f5de84f1f4ab39003b5e1a76000000b016b350913219c7ba5c82de2c03bc58ccfae0a61da64f8828d7027f800b66842f08410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb608410eb604e902e03d73ea1d22914921909159b03707726c113a4e44c3e5e1762b624f520ad71cc5e455dea6e08c7acfb6f5040f22fa83a742ec0c3b74ef6b79760e60f10000021c000000b02e6bb827cda199e1546fa4e49d4b0248aaaceb0a6d1680039b28a78d3616caab0c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb60c410eb61ca16a76d8fbbc441a7e0fda2a20032be6d3b758d801461f880c0983561295ce228f845c7fddb0cdd87941885083ad8ad2c6c89409b3041639159386a0bea76d000000b015bfd14b87f7cbde940c25e6b5585367324547aeba24074d1b6cda0670c7112610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb610410eb603f5839a9351ee415a1a90dc422d544a6e6c13fd250ecd6908503bfc90c2dc4909e39d803a33e2cb1815c28a6890fea95a5f253856c08b5fb959c5ffdb6eede8000000b02d7838e2237f9e058bf8ec9f4ee6fce2e2118c9b80eaff27df9302139b7757a214410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb614410eb61badeb312ed9c06852075794dbbbfdc61e3858e9ebd5c543cc766409bb7322c5219c0516d5bbb4f210028943021fa8250a2b6a251d87833a7d7fee0d061f34640000021c000000b014cc5205ddd5d002cb956da166f44e0169a9e93fcdf886715fd7348cd6279e1d18410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb618410eb603020454e92ff26591a3d896f3c94ee4a5d0b58e38e34c8d4cba9682f623694008f01e3a9011e6ef4f9f0a451a2cf94391c3c6c96a950a83fdc4208640cf7adf000000b02c84b99c795da229c382345a0082f77d19762e2c94bf7e4c23fd5c9a00d7e4991c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61c410eb61aba6beb84b7c48c89909f4f8d57f860559cfa7affaa446810e0be9020d3afbc20a885d12b99b916478bd0fdb3bba2bf41900bb6315c025ec1ea48936b7fc15b000000b013d8d2c033b3d427031eb55c1890489ba10e8ad0e1cd0595a4418f133b882b1420410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb620410eb6020e850f3f0df689c92d2051a565497edd35571f4cb7cbb19124f1095b83f63707fc9ef4e5efeb13872851ffcbc8f3ddc928685a7e6989a8422e7b0ca63007d60000021c000000b02b913a56cf3ba64dfb0b7c14b21ef21750dacfbda893fd706867b7206638719024410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb624410eb619c6eca5da95c8b0c119e70a3ef3f2fa8d019c0c137ec38c554b191686343cb31fb5068b8177bd3a7f1518b865579d5978f4ad47453081830654a319d0e04e52000000b012e5537a8991d84b3aa7fd16ca2c4335d8732c61f5a184b9e8abe999a0e8b80b28410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb628410eb6011b05c994ebfaae00b6680c570144191499f8b0608c4ad5d58f4b8fc0e4832e07091faf3bcdef37beb199ba7d64ee78008d09eb923e08cc8698d5930b9094cd000000b02a9dbb112519aa723294c3cf63baecb1883f714ebc687c94acd211a6cb98fe872c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb62c410eb618d36d603073ccd4f8a32ec4f08fed94c4663d9d275342b099b5739ceb94c9aa1ec18745d755c15eb69e607316f397f3b0594ed8590500a74abefda03640db4900000fa400000168000000b011f1d434df6fdc6f723144d17bc83dd00fd7cdf3097603de2d1644200649450230410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb630410eb600278683eac9fed2383fafc7089d3eb34bfe9a417460c9fa19f9a616264510250615a06991abf35bf63ae1752f00e91237f1ab7ca61287f0cb03301970f121c4000000b029aa3bcb7af7ae966a1e0b8a1556e74bbfa412dfd03cfbb8f13c6c2d30f98b7e34410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb634410eb617dfee1a8651d0f9302c767fa22be82efbcadf2e3b27c1d4de1fce2350f556a11dce08002d33c582ee27a82dc88f928de7bdf0696cd97fcb8f2958269ba1684000000168000000b010fe54ef354de093a9ba8c8c2d64386a473c6f841d4a830271809ea66ba9d1f938410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb638410eb62f9855b121d9a32028193d383bba91aaab97241b01eeb9afa245f6307ba59d1d05222123e789f7802dc4292fe09ce3ac6f564d0db9e707150f6d8a9fd651aebb000000b028b6bc85d0d5b2baa1a75344c6f2e1e5f708b470e4117add35a6c6b3965a18753c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb63c410eb616ec6ed4dc2fd51d67b5be3a53c7e2c9332f80bf4efc40f9228a28a9b655e3981cda88ba8311c9a725b0efe87a2b8d281f2291fa80adfeefd393b2ad0101f53700000168000000b0100ad5a98b2be4b7e143d446df0033047ea11115311f0226b5eaf92cd10a5ef040410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb640410eb62ea4d66b77b7a7445fa284f2ed568c44e2fbc5ac15c338d3e6b050b6e1062a14042ea1de3d67fba4654d70ea9238de46a6baee9ecdbb863953d7e5263bb23bb2000000b027c33d4026b3b6ded9309aff788edc802e6d5601f7e5fa017a112139fbbaa56c44410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb644410eb615f8ef8f320dd9419f3f05f50563dd636a94225062d0c01d66f483301bb6708f1be70974d8efcdcb5d3a37a32bc787c25687338b94827e1417fe0d336662822e00000168000000b00f175663e109e8dc18cd1c01909c2d9eb605b2a644f3814afa5553b3366aebe748410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb648410eb62db15725cd95ab68972bccad9ef286df1a60673d2997b7f82b1aab3d4666b70b033b22989345ffc89cd6b8a543d4d8e0de1f902fe190055d98423faca112c8a9000000b026cfbdfa7c91bb0310b9e2ba2a2ad71a65d1f7930bba7925be7b7bc0611b32634c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb64c410eb61505704987ebdd65d6c84dafb6ffd7fda1f8c3e176a53f41ab5eddb68116fd861af38a2f2ecdd1ef94c37f5ddd63825c8debd51ca856fd385c6867b9cbc30f2500000168000000b00e23d71e36e7ed00505663bc42382838ed6a543758c8006f3ebfae399bcb78de50410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb650410eb62cbdd7e02373af8cceb51468508e817951c508ce3d6c371c6f8505c3abc744020247a352e92403ecd460005ff570d37b158431c0f5648481dcac9a33067355a0000000b025dc3eb4d26fbf2748432a74dbc6d1b49d3699241f8ef84a02e5d646c67bbf5a54410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb654410eb61411f103ddc9e18a0e51956a689bd297d95d65728a79be65efc9383ce6778a7d1a000ae984abd613cc4cc7188eff7cf6c55076adbc2b7c5ca0d2c24031239c1c00000168000000b00d3057d88cc5f12487dfab76f3d422d324cef5c86c9c7f93832a08c0012c05d558410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb658410eb62bca589a7951b3b1063e5c23022a7c138929aa5f5140b640b3ef604a1127d0f90154240d3f0208110be9481aa70cce154ce8d352093903a62116f4b96bd3e297000000b024e8bf6f284dc34b7fcc722f8d62cc4ed49b3ab53363776e475030cd2bdc4c515c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb65c410eb6131e71be33a7e5ae45dadd251a37cd3210c207039e4e3d8a343392c34bd81774190c8ba3da89da3803d60ed3409b7790fcb5183ecffffb80e53d1cc69684291300000168000000b00c3cd892e2a3f548bf68f331a5701d6d5c3397598070feb7c7946346668c92cc60410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb660410eb62ad6d954cf2fb7d53dc7a3ddb3c676adc08e4bf065153564f859bad076885df00060a4c794e00c3543728fd558a8c8af844d74e31d0d82ca65814f3fd1346f8e000000b023f540297e2bc76fb755b9ea3efec6e90bffdc464737f6928bba8b53913cd94864410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb664410eb6122af2788985e9d27d6424dfcbd3c7cc4826a894b222bcae789ded49b138a46b18190c5e3067de5c3b5f568df237722b3419b9cfe3d47aa529a7774cfbe4b60a00000168000000b00b49594d3881f96cf6f23aec570c1807939838ea94457ddc0bfebdcccbed1fc368410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb668410eb629e35a0f250dbbf97550eb9865627147f7f2ed8178e9b4893cc41556dbe8eae72fd173f4cbefb083334c1d468bc61ba6e3e5febcaa9b727fedcd9f5a2694fc86000000b02301c0e3d409cb93eedf01a4f09ac18343647dd75b0c75b6d024e5d9f69d663f6c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb66c410eb611377332df63edf6b4ed6c9a7d6fc2667f8b4a25c5f73bd2bd0847d01699316217258d188645e28072e89e48a3d36cc56b7e5b60f7a8f9c96e11d1d36145430100000168000000b00a55da078e5ffd912e7b82a708a812a1cafcda7ba819fd0050691853314dacba70410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb670410eb628efdac97aebc01dacda335316fe6be22f578f128cbe33ad812e6fdd414977de2eddf4af21cdb4a76ad565013d6216411b4aa04dbe6ff1a43237f9e08bf5897d000000b0220e419e29e7cfb82668495fa236bc1d7ac91f686ee0f4db148f40605bfdf33674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb674410eb61043f3ed3541f21aec76b4552f0bbd00b6efebb6d9cbbaf70172a2567bf9be5916320dd2dc23e6a4aa71e603556f675fa2e2fcf20b7d78edb27c2c59c6a5cff800000168000000b009625ac1e43e01b56604ca61ba440d3c02617c0cbbee7c2494d372d996ae39b178410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb678410eb627fc5b83d0c9c441e4637b0dc89a667c66bc30a3a092b2d1c598ca63a6aa04d52dea756977abb8cba25eacbbeefe10db52af41ded24470c876a25466f1561674000000b0211ac2587fc5d3dc5df1911a53d2b6b7b22dc0f982b573ff58f99ae6c15e802d7c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb67c410eb60f5074a78b1ff63f23fffc0fe0a7b79aee548d47eda03a1b45dcfcdce15a4b50153e8e8d3201eac8e1fb2dbe070b61f9da479e831f51f811f6e686e02c065cef00000168000000b0086edb7c3a1c05d99d8e121c6be007d639c61d9dcfc2fb48d93dcd5ffc0ec6a880410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb680410eb62708dc3e26a7c8661becc2c87a3661169e20d234b46731f60a0324ea0c0a91cc2cf6f623cd89bcefd9e7f476a09a0b758a13e36fe618efecbb0caeed56b6a36b000000b020274312d5a3d800957ad8d5056eb151e992628a9689f3239d63f56d26bf0d2484410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb684410eb60e5cf561e0fdfa635b8943ca9243b23525b92ed90174b93f8a47576346bad847144b0f4787dfeeed19847578b8a75c9411ac4014332677363b50e1669166e9e6", "txsEffectsHash": "0x53472a10e3068a8dcdc64c5d353bc5b93d293459f513c087e52471bc28094012", "decodedHeader": { "contentCommitment": { @@ -77,7 +59,7 @@ "stateReference": { "l1ToL2MessageTree": { "nextAvailableLeafIndex": 16, - "root": "0x0a241c83a063083fad29b6c333afcd968f71f8a875544ff1f1f08cae7f770f51" + "root": "0x1864fcdaa80ff2719154fa7c8a9050662972707168d69eac9db6fd3110829f80" }, "partialStateReference": { "noteHashTree": { @@ -95,8 +77,7 @@ } } }, - "header": "0x012a86560737adb075e12af8253fb09abf17aa841fb56d180bc89f0d2d473c7f00000001000000000000000000000000000000000000000000000000000000000000000253472a10e3068a8dcdc64c5d353bc5b93d293459f513c087e52471bc28094012536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123cc2db86162c987f9328539ebf11947c30e3846f8bdb7a820aed2bbabd9544b9dc0a241c83a063083fad29b6c333afcd968f71f8a875544ff1f1f08cae7f770f510000001002c672a4d7bd90c4b6ba35bbc9906598862f626554be3cba05de19265a8ece71000001000ed22b14764d5756c4e97521b31e93e21192b98b3bc2e2559e07b1263ce7b1be000001801faf8e36b0fb8fb337acc1c32316e1fcbd0465d53c47a2dd73ebb031042566cb000000c00000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a43e0eb6a43e0eb6a43e0eb6a43e0eb6a43e0eb615a9c4f4c75d79ce22330ca2cdb6c1a6ede1f6d94ba28016eeb25e5578913ccb", - "l1ToL2MessagesHash": "0xb213c9c543fce2a66720d26a913fe0d018f72a47ccfe698baafcf4cced343cfd", - "publicInputsHash": "0x1b0a1c6994b2a08a07bc46473e2c220b886b9be4da98a4ee45fd37734c1c60db" + "header": "0x012a86560737adb075e12af8253fb09abf17aa841fb56d180bc89f0d2d473c7f00000001000000000000000000000000000000000000000000000000000000000000000253472a10e3068a8dcdc64c5d353bc5b93d293459f513c087e52471bc28094012536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123cc2db86162c987f9328539ebf11947c30e3846f8bdb7a820aed2bbabd9544b9dc1864fcdaa80ff2719154fa7c8a9050662972707168d69eac9db6fd3110829f800000001002c672a4d7bd90c4b6ba35bbc9906598862f626554be3cba05de19265a8ece71000001000ed22b14764d5756c4e97521b31e93e21192b98b3bc2e2559e07b1263ce7b1be000001801faf8e36b0fb8fb337acc1c32316e1fcbd0465d53c47a2dd73ebb031042566cb000000c00000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a43e0eb6a43e0eb6a43e0eb6a43e0eb6a43e0eb615a9c4f4c75d79ce22330ca2cdb6c1a6ede1f6d94ba28016eeb25e5578913ccb", + "publicInputsHash": "0x1886f8de5d2fb15dcea34eb26903eb805207937f1145e979bfb81f1844f23564" } } \ No newline at end of file diff --git a/l1-contracts/test/fixtures/mixed_block_1.json b/l1-contracts/test/fixtures/mixed_block_1.json index 271ba99450e..e9c7270ddd6 100644 --- a/l1-contracts/test/fixtures/mixed_block_1.json +++ b/l1-contracts/test/fixtures/mixed_block_1.json @@ -22,24 +22,6 @@ "sender": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" }, "messages": { - "l1ToL2Messages": [ - "0x12e5643e26da426570dd999e0e044e5f83d60f3cd813c55059bc0ea0f4a7c9d4", - "0x13b2d2cea949fa0876265cd8eee3a4dce1e243783562ea07e2de537f469f7bf6", - "0x27abb3d4560e786bafd52f77ac52dea36c50419f32386567bd969b0c38e1bd74", - "0x05d339cecb99fa74bfd631917f965357f0341d8c4dfe6a8f4b621d8df54c8294", - "0x1d560ac24523012499eeca2594f20ce238b7d21b98ad5c638767d70ee05532c2", - "0x183e6d64e69b005709dfc4771c7ca5981adc54f3e8bd1b00222461ae90e44eda", - "0x2f1d4572fe0b0b23100a7ea6d4780b25e826e41ca9e71897e010bcb218887b3d", - "0x036d44eb30a430b5cfc6660270eb5fb7f274689ac73dfa53ba9d0739fe38637f", - "0x01f7130af8c5888d4c65ea68109a1d58fe8d7d1ae62098857b0a3a3dcd393ef8", - "0x0ed8bcba6eb5e3b4887a32c51c5853b97a5eb59c87c520f36423c6d7be060718", - "0x21ca1719330de5e3c51a521778b49cbbc1d53f8ed42e0562bf21ed72e8cb9a41", - "0x0b0e82ef5f7276df41353faef675fb408aeb246b6e2f704cab99bf28d1427e7b", - "0x0fec7b9929c6effdcd045d3fdcec1d1b73faed495444699fab936630d69b519f", - "0x0bf4f1453db36439419dbfba703b849d1aa5774fe9a5325e5bf3e7cdda9d1f7f", - "0x2a9c2a035c72fd4a3e383306aff03448f9accc08a2c665837560cec8675fe251", - "0x2bfaef35a8fb7df08a81001a7fd4903849c0371dedd87f31295f46269c5205dd" - ], "l2ToL1Messages": [ "0x0000000000000000000000000000000000000000000000000000000000000440", "0x0000000000000000000000000000000000000000000000000000000000000441", @@ -52,12 +34,12 @@ ] }, "block": { - "archive": "0x1c4046feb368b147423eeb63abf02b5ef4b825e5374fac801af5389bf50604b5", - "body": "0x0000001012e5643e26da426570dd999e0e044e5f83d60f3cd813c55059bc0ea0f4a7c9d413b2d2cea949fa0876265cd8eee3a4dce1e243783562ea07e2de537f469f7bf627abb3d4560e786bafd52f77ac52dea36c50419f32386567bd969b0c38e1bd7405d339cecb99fa74bfd631917f965357f0341d8c4dfe6a8f4b621d8df54c82941d560ac24523012499eeca2594f20ce238b7d21b98ad5c638767d70ee05532c2183e6d64e69b005709dfc4771c7ca5981adc54f3e8bd1b00222461ae90e44eda2f1d4572fe0b0b23100a7ea6d4780b25e826e41ca9e71897e010bcb218887b3d036d44eb30a430b5cfc6660270eb5fb7f274689ac73dfa53ba9d0739fe38637f01f7130af8c5888d4c65ea68109a1d58fe8d7d1ae62098857b0a3a3dcd393ef80ed8bcba6eb5e3b4887a32c51c5853b97a5eb59c87c520f36423c6d7be06071821ca1719330de5e3c51a521778b49cbbc1d53f8ed42e0562bf21ed72e8cb9a410b0e82ef5f7276df41353faef675fb408aeb246b6e2f704cab99bf28d1427e7b0fec7b9929c6effdcd045d3fdcec1d1b73faed495444699fab936630d69b519f0bf4f1453db36439419dbfba703b849d1aa5774fe9a5325e5bf3e7cdda9d1f7f2a9c2a035c72fd4a3e383306aff03448f9accc08a2c665837560cec8675fe2512bfaef35a8fb7df08a81001a7fd4903849c0371dedd87f31295f46269c5205dd00000004380000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024100000000000000000000000000000000000000000000000000000000000002420000000000000000000000000000000000000000000000000000000000000243000000000000000000000000000000000000000000000000000000000000024400000000000000000000000000000000000000000000000000000000000002450000000000000000000000000000000000000000000000000000000000000246000000000000000000000000000000000000000000000000000000000000024700000000000000000000000000000000000000000000000000000000000002480000000000000000000000000000000000000000000000000000000000000249000000000000000000000000000000000000000000000000000000000000024a000000000000000000000000000000000000000000000000000000000000024b000000000000000000000000000000000000000000000000000000000000024c000000000000000000000000000000000000000000000000000000000000024d000000000000000000000000000000000000000000000000000000000000024e000000000000000000000000000000000000000000000000000000000000024f0000000000000000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000000000000000000025100000000000000000000000000000000000000000000000000000000000002520000000000000000000000000000000000000000000000000000000000000253000000000000000000000000000000000000000000000000000000000000025400000000000000000000000000000000000000000000000000000000000002550000000000000000000000000000000000000000000000000000000000000256000000000000000000000000000000000000000000000000000000000000025700000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000259000000000000000000000000000000000000000000000000000000000000025a000000000000000000000000000000000000000000000000000000000000025b000000000000000000000000000000000000000000000000000000000000025c000000000000000000000000000000000000000000000000000000000000025d000000000000000000000000000000000000000000000000000000000000025e000000000000000000000000000000000000000000000000000000000000025f0000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026100000000000000000000000000000000000000000000000000000000000002620000000000000000000000000000000000000000000000000000000000000263000000000000000000000000000000000000000000000000000000000000026400000000000000000000000000000000000000000000000000000000000002650000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000026700000000000000000000000000000000000000000000000000000000000002680000000000000000000000000000000000000000000000000000000000000269000000000000000000000000000000000000000000000000000000000000026a000000000000000000000000000000000000000000000000000000000000026b000000000000000000000000000000000000000000000000000000000000026c000000000000000000000000000000000000000000000000000000000000026d000000000000000000000000000000000000000000000000000000000000026e000000000000000000000000000000000000000000000000000000000000026f00000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000027200000000000000000000000000000000000000000000000000000000000002730000000000000000000000000000000000000000000000000000000000000274000000000000000000000000000000000000000000000000000000000000027500000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000277370000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000003420000000000000000000000000000000000000000000000000000000000000343000000000000000000000000000000000000000000000000000000000000034400000000000000000000000000000000000000000000000000000000000003450000000000000000000000000000000000000000000000000000000000000346000000000000000000000000000000000000000000000000000000000000034700000000000000000000000000000000000000000000000000000000000003480000000000000000000000000000000000000000000000000000000000000349000000000000000000000000000000000000000000000000000000000000034a000000000000000000000000000000000000000000000000000000000000034b000000000000000000000000000000000000000000000000000000000000034c000000000000000000000000000000000000000000000000000000000000034d000000000000000000000000000000000000000000000000000000000000034e000000000000000000000000000000000000000000000000000000000000034f0000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000035100000000000000000000000000000000000000000000000000000000000003520000000000000000000000000000000000000000000000000000000000000353000000000000000000000000000000000000000000000000000000000000035400000000000000000000000000000000000000000000000000000000000003550000000000000000000000000000000000000000000000000000000000000356000000000000000000000000000000000000000000000000000000000000035700000000000000000000000000000000000000000000000000000000000003580000000000000000000000000000000000000000000000000000000000000359000000000000000000000000000000000000000000000000000000000000035a000000000000000000000000000000000000000000000000000000000000035b000000000000000000000000000000000000000000000000000000000000035c000000000000000000000000000000000000000000000000000000000000035d000000000000000000000000000000000000000000000000000000000000035e000000000000000000000000000000000000000000000000000000000000035f0000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036100000000000000000000000000000000000000000000000000000000000003620000000000000000000000000000000000000000000000000000000000000363000000000000000000000000000000000000000000000000000000000000036400000000000000000000000000000000000000000000000000000000000003650000000000000000000000000000000000000000000000000000000000000366000000000000000000000000000000000000000000000000000000000000036700000000000000000000000000000000000000000000000000000000000003680000000000000000000000000000000000000000000000000000000000000369000000000000000000000000000000000000000000000000000000000000036a000000000000000000000000000000000000000000000000000000000000036b000000000000000000000000000000000000000000000000000000000000036c000000000000000000000000000000000000000000000000000000000000036d000000000000000000000000000000000000000000000000000000000000036e000000000000000000000000000000000000000000000000000000000000036f00000000000000000000000000000000000000000000000000000000000003700000000000000000000000000000000000000000000000000000000000000371000000000000000000000000000000000000000000000000000000000000037200000000000000000000000000000000000000000000000000000000000003730000000000000000000000000000000000000000000000000000000000000374000000000000000000000000000000000000000000000000000000000000037500000000000000000000000000000000000000000000000000000000000003760200000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000441100000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064a0000000000000000000000000000000000000000000000000000000000000641000000000000000000000000000000000000000000000000000000000000064b0000000000000000000000000000000000000000000000000000000000000642000000000000000000000000000000000000000000000000000000000000064c0000000000000000000000000000000000000000000000000000000000000643000000000000000000000000000000000000000000000000000000000000064d0000000000000000000000000000000000000000000000000000000000000644000000000000000000000000000000000000000000000000000000000000064e0000000000000000000000000000000000000000000000000000000000000645000000000000000000000000000000000000000000000000000000000000064f00000000000000000000000000000000000000000000000000000000000006460000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064700000000000000000000000000000000000000000000000000000000000006510000000000000000000000000000000000000000000000000000000000000648000000000000000000000000000000000000000000000000000000000000065200000000000000000000000000000000000000000000000000000000000006490000000000000000000000000000000000000000000000000000000000000653000000000000000000000000000000000000000000000000000000000000064a0000000000000000000000000000000000000000000000000000000000000654000000000000000000000000000000000000000000000000000000000000064b0000000000000000000000000000000000000000000000000000000000000655000000000000000000000000000000000000000000000000000000000000064c0000000000000000000000000000000000000000000000000000000000000656000000000000000000000000000000000000000000000000000000000000064d0000000000000000000000000000000000000000000000000000000000000657000000000000000000000000000000000000000000000000000000000000064e0000000000000000000000000000000000000000000000000000000000000658000000000000000000000000000000000000000000000000000000000000064f0000000000000000000000000000000000000000000000000000000000000659000011000000021c000000b0077b5c368ffa09fdd51759d71d7c0270712abf2ee3977a6d1da827e6616f539f88410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb626155cf87c85cc8a53760a832bd25bb0d58573c5c83bb11a4e6d7f70716b1ec32c0376de2367c11411713c315236060fc1788500f9ed6f10ff770973bc173062000000b01f33c3cd2b81dc24cd04208fb70aabec20f7041baa5e7247e1ce4ff38c1f9a1b8c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb60d69761c36dbfe8793128b8543dfaccf5d1dd06a15493863ceb1b1e9ac1b653e13579001ddbdf311510dbd336a43572e4910e1a546faf65a7fbb3becf6c776dd000000b00687dcf0e5d80e220ca0a191cf17fd0aa88f60bff76bf9916212826cc6cfe09690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb62521ddb2d263d0ae8aff523ddd6e564b0cea1556dc10303e92d7d9f6d6cbabba2b0ff7987945c53848fa83ec03d200a9f8dd26920dc1ee3543e163fa2177bd590000021c000000b01e404487815fe049048d684a68a6a686585ba5acbe32f16c2638aa79f180271294410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb60c75f6d68cba02abca9bd33ff57ba769948271fb291db788131c0c70117bf235126410bc339bf735889704ee1bdf51c8807583365acf757ec42596735c2803d4000000b005945dab3bb612464429e94c80b3f7a4dff402510b4078b5a67cdcf32c306d8d98410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb6242e5e6d2841d4d2c28899f88f0a50e5444eb6e7efe4af62d742347d3c2c38b12a1c7852cf23c95c8083cba6b56dfb443041c82321966d59884bbe8086d84a50000000b01d4cc541d73de46d3c16b0051a42a1208fc0473dd20770906aa3050056e0b4099c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb60b827790e29806d002251afaa717a203cbe7138c3cf236ac578666f676dc7f2c117091768979fb59c0204ca8cd7b4c62b7da24c76ea3f4a3088ff0f9c18890cb0000021c000000b004a0de659194166a7bb33107324ff23f1758a3e21f14f7d9eae737799190fa84a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6233adf277e1fd8f6fa11e1b340a64b7f7bb3587903b92e871bac8f03a18cc5a82928f90d2501cd80b80d13616709f5de67a669b4356aec7dccb61906ec38d747000000b01c5945fc2d1be891739ff7bfcbde9bbac724e8cee5dbefb4af0d5f86bc414100a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb60a8ef84b38760af439ae62b558b39c9e034bb51d50c6b5d09bf0c17cdc3d0c23107d1230df57ff7df7a994637f1746fcef3ec658827873c74cfa4b8026e91dc2000000b003ad5f1fe7721a8eb33c78c1e3ebecd94ebd457332e976fe2f5191fff6f1877ba8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb622475fe1d3fddd1b319b296df2424619b317fa0a178dadab6016e98a06ed529f283579c77adfd1a4ef965b1c18a5f0789f0b0b45493f6ba21120738d5199643e0000021c000000b01b65c6b682f9ecb5ab293f7a7d7a9654fe898a5ff9b06ed8f377ba0d21a1cdf7ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6099b79058e540f187137aa700a4f97383ab056ae649b34f4e05b1c03419d991a0f8992eb353603a22f32dc1e30b3419726a367e9964cf2eb9164a6068c49aab9000000b002b9dfda3d501eb2eac5c07c9587e7738621e70446bdf62273bbec865c521472b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb62153e09c29dbe13f69247128a3de40b3ea7c9b9b2b622ccfa48144106c4ddf962741fa81d0bdd5c9271fa2d6ca41eb12d66facd65d13eac6558ace13b6f9f135000000b01a724770d8d7f0d9e2b287352f1690ef35ee2bf10d84edfd37e2149387025aeeb4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb608a7f9bfe432133ca8c0f22abbeb91d27214f83f786fb41924c57689a6fe26110e9613a58b1407c666bc23d8e24f3c315e08097aaa21720fd5cf008cf1aa37b00000021c000000b001c66094932e22d7224f08374723e20dbd8688955a927546b826470cc1b2a169b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6206061567fb9e563a0adb8e3557a3b4e21e13d2c3f36abf3e8eb9e96d1ae6c8d264e7b3c269bd9ed5ea8ea917bdde5ad0dd44e6770e869ea99f5289a1c5a7e2c000000b0197ec82b2eb5f4fe1a3bceefe0b28b896d52cd8221596d217c4c6f19ec62e7e5bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb607b47a7a3a101760e04a39e56d878c6ca97999d08c44333d692fd1100c5eb3080da2945fe0f20bea9e456b9393eb36cb956cab0bbdf5f1341a395b13570ac4a7000000b000d2e14ee90c26fb59d84ff1f8bfdca7f4eb2a266e66f46afc90a19327132e60c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb61f6ce210d597e987d837009e071635e85945debd530b2b182d55f91d370ef984255afbf67c79de119632324c2d79e0474538eff884bce90ede5f832081bb0b230000021c000000b0188b48e58493f92251c516aa924e8623a4b76f13352dec45c0b6c9a051c374dcc4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb606c0fb348fee1b8517d381a01f238706e0de3b61a018b261ad9a2b9671bf3fff0caf151a36d0100ed5ceb34e45873165ccd14c9cd1ca70585ea3b599bc6b519e000000b03043b07c201bcb4949b1dd632bdd2f9f5483b3fffbf4e42084dcf1ad7c73bb58c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb61e7962cb2b75edac0fc04858b8b2308290aa804e66dfaa3c71c053a39c6f867b24677cb0d257e235cdbb7a06df15dae17c9d91899891683322c9dda6e71b981a000000b01797c99fda71fd46894e5e6543ea80bddc1c10a449026b6a05212426b72401d3cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb605cd7beee5cc1fa94f5cc95ad0bf81a11842dcf2b3ed3185f204861cd71fccf60bbb95d48cae14330d57fb08f7232c000435ee2de59eef7ca30e102021cbde950000021c000000b02f50313675f9cf6d813b251ddd792a398be855910fc96344c9474c33e1d4484fd0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb61d85e3858153f1d0474990136a4e2b1cc80f21df7ab42960b62aae2a01d013722373fd6b2835e65a0544c1c190b1d57bb402331aac65e7576734382d4c7c2511000000b016a44a5a3050016ac0d7a61ff5867b581380b2355cd6ea8e498b7ead1c848ecad4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb604d9fca93baa23cd86e61115825b7c3b4fa77e83c7c1b0aa366ee0a33c8059ed0ac8168ee28c185744e142c3a8bf269a3b9a8fbef9736ea0e7786aa6872c6b8c000000b02e5cb1f0cbd7d391b8c46cd88f1524d3c34cf722239de2690db1a6ba4734d546d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb61c92643fd731f5f47ed2d7ce1bea25b6ff73c3708e88a884fa9508b06730a06922807e257e13ea7e3cce097c424dd015eb66d4abc03a667bab9e92b3b1dcb2080000021c000000b015b0cb14862e058ef860eddaa72275f24ae553c670ab69b28df5d93381e51bc1dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb603e67d63918827f1be6f58d033f776d5870c2014db962fce7ad93b29a1e0e6e409d49749386a1c7b7c6a8a7e5a5b213472ff31500d47edc52be2c52cec8cf883000000b02d6932ab21b5d7b5f04db49340b11f6dfab198b33772618d521c0140ac95623de0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb61b9ee4fa2d0ffa18b65c1f88cd86205136d86501a25d27a93eff6336cc912d60218cfedfd3f1eea274575136f3e9cab022cb763cd40ee59ff008ed3a173d3eff000000b014bd4bcedc0c09b32fea359558be708c8249f557847fe8d6d26033b9e745a8b8e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb602f2fe1de7662c15f5f8a08ae593716fbe70c1a5ef6aaef2bf4395b0074173db08e118038e48209fb3f3d2390bf71bceaa63d2e1211c6ce9704d1fb351ed857a00000fa400000168000000b02c75b3657793dbda27d6fc4df24d1a0832163a444b46e0b196865bc711f5ef34e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb61aab65b482edfe3cede567437f221aeb6e3d0692b631a6cd8369bdbd31f1ba5720997f9a29cff2c6abe098f1a585c54a5a3017cde7e364c4347347c07c9dcbf6000000b013c9cc8931ea0dd767737d500a5a6b26b9ae96e8985467fb16ca8e404ca635afec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb601ff7ed83d44303a2d81e845972f6c09f5d56337033f2e1703adf0366ca200d207ed98bde42624c3eb7d19f3bd931668e1c8747234f0ec0db4b77a39b74e127100000168000000b02b82341fcd71dffe5f604408a3e914a2697adbd55f1b5fd5daf0b64d77567c2bf0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb619b7e66ed8cc0261256eaefe30be1585a5a1a823ca0625f1c7d418439752474e1fa600547fadf6eae369e0ac5721bfe49194b95efbb7e3e878dda246e1fe58ed000000b012d64d4387c811fb9efcc50abbf665c0f1133879ac28e71f5b34e8c6b206c2a6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6010bff929322345e650b300048cb66a42d3a04c81713ad3b48184abcd2028dc906fa19783a0428e8230661ae6f2f1103192d160348c56b31f921d4c01cae9f6800000168000000b02a8eb4da234fe42296e98bc355850f3ca0df7d6672efdefa1f5b10d3dcb70922f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb618c467292eaa06855cf7f6b8e25a101fdd0649b4dddaa5160c3e72c9fcb2d4451eb2810ed58bfb0f1af3286708bdba7ec8f95af00f8c630cbd47fccd475ee5e4000000b011e2cdfddda6161fd6860cc56d92605b2877da0abffd66439f9f434d17674f9dfc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb60018804ce90038829c9477bafa67613e649ea6592ae82c5f8c82a54337631ac006069a328fe22d0c5a8fa96920cb0b9d5091b7945c99ea563d8c2f46820f2c5f00000168000000b02a02bfaa4936733a7d8dfc0d2758c4586ea3a2a143cbfe699401cc90b740a34a00420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb617d0e7e384880aa994813e7393f60aba146aeb45f1af243a50a8cd506213613c1dbf01c92b69ff33527c7021ba59b519005dfc812360e23101b25753acbf72db000000b01156d8ce038ca537bd2a7d0f3f661576f63bff4590d985b31445ff09f1f0e9c504420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb62ff0d98ff01867c43b892dbb4dbc6eb75a96b3dc757dbc60450b569401ecb4e9057aa502b5c8bc24413419b2f29ec0b91e55dccf2d7609c5b232eb035c98c68700000168000000b0290f40649f14775eb51743c7d8f4bef2a608443257a07d8dd86c27171ca1304108420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb61744f2b3aa6e99c17b25aebd65c9bfd5e22f1080c28b43a9c54f890d3c9cfb641d330c9951508e4b3920e06b8c2d6a34ce2221bbf43d01a07659131087490d03000000b010635988596aa95bf4b3c4c9f10210112da0a0d6a4ae04d758b05990575176bc0c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb62efd5a4a45f66be873127575ff58695191fb556d89523b848975b11a674d41e0048725bd0ba6c04878bd616da43abb5355ba7e60414a88e9f69d4589c1f9537e00000168000000b0281bc11ef4f27b82eca08b828a90b98cdd6ce5c36b74fcb21cd6819d8201bd3810420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb61651736e004c9de5b2aef6781765ba701993b211d65fc2ce09b9e393a1fd885b1c3f8d53a72e926f70aa28263dc964cf0586c34d081180c4bac36d96eca999fa000000b00f6fda42af48ad802c3d0c84a29e0aab65054267b88283fb9d1ab416bcb203b314420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb62e09db049bd4700caa9bbd30b0f463ebc95ff6fe9d26baa8cde00ba0ccadced70393a6776184c46cb046a92855d6b5ed8d1f1ff1551f080e3b07a0102759e07500000168000000b0272841d94ad07fa72429d33d3c2cb42714d187547f497bd66140dc23e7624a2f18420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb6155df428562aa209ea383e32c901b50a50f853a2ea3441f24e243e1a075e15521b4c0e0dfd0c9693a8336fe0ef655f693ceb64de1be5ffe8ff2dc81d520a26f1000000b00e7c5afd0526b1a463c6543f543a05459c69e3f8cc57031fe1850e9d221290aa1c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb62d165bbef1b27430e22504eb62905e8600c4988fb0fb39cd124a6627320e5bce02a02731b762c890e7cff0e30772b087c483c18268f387327f71fa968cba6d6c00000168000000b02634c293a0ae83cb5bb31af7edc8aec14c3628e5931dfafaa5ab36aa4cc2d72620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb6146a74e2ac08a62e21c185ed7a9dafa4885cf533fe08c116928e98a06cbea2491a588ec852ea9ab7dfbcb79ba1015a037450066f2fba7f0d439822a3b76ab3e8000000b00d88dbb75b04b5c89b4f9bfa05d5ffdfd3ce8589e02b824425ef692387731da124420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb62c22dc794790785519ae4ca6142c592038293a20c4cfb8f156b4c0ad976ee8c501aca7ec0d40ccb51f59389db90eab21fbe863137cc80656c3dc551cf21afa6300000168000000b02541434df68c87ef933c62b29f64a95b839aca76a6f27a1eea159130b223641d28420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb61376f59d01e6aa52594acda82c39aa3ebfc196c511dd403ad6f8f326d21f2f4019650f82a8c89edc1745ff56529d549dabb4a800438efe3188027d2a1ccb40df000000b00c955c71b0e2b9ecd2d8e3b4b771fa7a0b33271af40001686a59c3a9ecd3aa982c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62b2f5d339d6e7c7951379460c5c853ba6f8ddbb1d8a438159b1f1b33fccf75bc00b928a6631ed0d956e280586aaaa5bc334d04a4909c857b0846afa3577b875a00000168000000b0244dc4084c6a8c13cac5aa6d5100a3f5baff6c07bac6f9432e7febb71783f11430420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb61283765757c4ae7690d41562ddd5a4d8f726385625b1bf5f1b634dad377fbc371871903cfea6a3004ecf471104394f37e319499157637d55cc6cd7b0822bcdd6000000b00ba1dd2c06c0be110a622b6f690df5144297c8ac07d4808caec41e305234378f34420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb62a3bddedf34c809d88c0dc1b77644e54a6f27d42ec78b739df8975ba623002b33029f7d39a2e752746bc0dc99dc7f8b392e58e7e1e2a75309092ffbdacdc145200000168000000b0235a44c2a2489038024ef228029c9e8ff2640d98ce9b786772ea463d7ce47e0b38420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb6118ff711ada2b29ac85d5d1d8f719f732e8ad9e739863e835fcda8339ce0492e177e10f75484a72486588ecbb5d549d21a7deb226b37fc7a10d73236e78c5acd000000b00aae5de65c9ec23541eb732a1aa9efae79fc6a3d1ba8ffb0f32e78b6b794c4863c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb629485ea8492a84c1c04a23d6290048eede571ed4004d365e23f3d040c7908faa2f36788df00c794b7e4555844f63f34dca4a300f31fef454d4fd5a44123ca149380000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000028100000000000000000000000000000000000000000000000000000000000002820000000000000000000000000000000000000000000000000000000000000283000000000000000000000000000000000000000000000000000000000000028400000000000000000000000000000000000000000000000000000000000002850000000000000000000000000000000000000000000000000000000000000286000000000000000000000000000000000000000000000000000000000000028700000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000289000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000028b000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028d000000000000000000000000000000000000000000000000000000000000028e000000000000000000000000000000000000000000000000000000000000028f0000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000029100000000000000000000000000000000000000000000000000000000000002920000000000000000000000000000000000000000000000000000000000000293000000000000000000000000000000000000000000000000000000000000029400000000000000000000000000000000000000000000000000000000000002950000000000000000000000000000000000000000000000000000000000000296000000000000000000000000000000000000000000000000000000000000029700000000000000000000000000000000000000000000000000000000000002980000000000000000000000000000000000000000000000000000000000000299000000000000000000000000000000000000000000000000000000000000029a000000000000000000000000000000000000000000000000000000000000029b000000000000000000000000000000000000000000000000000000000000029c000000000000000000000000000000000000000000000000000000000000029d000000000000000000000000000000000000000000000000000000000000029e000000000000000000000000000000000000000000000000000000000000029f00000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a100000000000000000000000000000000000000000000000000000000000002a200000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000002a400000000000000000000000000000000000000000000000000000000000002a500000000000000000000000000000000000000000000000000000000000002a600000000000000000000000000000000000000000000000000000000000002a700000000000000000000000000000000000000000000000000000000000002a800000000000000000000000000000000000000000000000000000000000002a900000000000000000000000000000000000000000000000000000000000002aa00000000000000000000000000000000000000000000000000000000000002ab00000000000000000000000000000000000000000000000000000000000002ac00000000000000000000000000000000000000000000000000000000000002ad00000000000000000000000000000000000000000000000000000000000002ae00000000000000000000000000000000000000000000000000000000000002af00000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002b100000000000000000000000000000000000000000000000000000000000002b200000000000000000000000000000000000000000000000000000000000002b300000000000000000000000000000000000000000000000000000000000002b400000000000000000000000000000000000000000000000000000000000002b500000000000000000000000000000000000000000000000000000000000002b600000000000000000000000000000000000000000000000000000000000002b7370000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000038100000000000000000000000000000000000000000000000000000000000003820000000000000000000000000000000000000000000000000000000000000383000000000000000000000000000000000000000000000000000000000000038400000000000000000000000000000000000000000000000000000000000003850000000000000000000000000000000000000000000000000000000000000386000000000000000000000000000000000000000000000000000000000000038700000000000000000000000000000000000000000000000000000000000003880000000000000000000000000000000000000000000000000000000000000389000000000000000000000000000000000000000000000000000000000000038a000000000000000000000000000000000000000000000000000000000000038b000000000000000000000000000000000000000000000000000000000000038c000000000000000000000000000000000000000000000000000000000000038d000000000000000000000000000000000000000000000000000000000000038e000000000000000000000000000000000000000000000000000000000000038f0000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000039100000000000000000000000000000000000000000000000000000000000003920000000000000000000000000000000000000000000000000000000000000393000000000000000000000000000000000000000000000000000000000000039400000000000000000000000000000000000000000000000000000000000003950000000000000000000000000000000000000000000000000000000000000396000000000000000000000000000000000000000000000000000000000000039700000000000000000000000000000000000000000000000000000000000003980000000000000000000000000000000000000000000000000000000000000399000000000000000000000000000000000000000000000000000000000000039a000000000000000000000000000000000000000000000000000000000000039b000000000000000000000000000000000000000000000000000000000000039c000000000000000000000000000000000000000000000000000000000000039d000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000039f00000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a100000000000000000000000000000000000000000000000000000000000003a200000000000000000000000000000000000000000000000000000000000003a300000000000000000000000000000000000000000000000000000000000003a400000000000000000000000000000000000000000000000000000000000003a500000000000000000000000000000000000000000000000000000000000003a600000000000000000000000000000000000000000000000000000000000003a700000000000000000000000000000000000000000000000000000000000003a800000000000000000000000000000000000000000000000000000000000003a900000000000000000000000000000000000000000000000000000000000003aa00000000000000000000000000000000000000000000000000000000000003ab00000000000000000000000000000000000000000000000000000000000003ac00000000000000000000000000000000000000000000000000000000000003ad00000000000000000000000000000000000000000000000000000000000003ae00000000000000000000000000000000000000000000000000000000000003af00000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b100000000000000000000000000000000000000000000000000000000000003b200000000000000000000000000000000000000000000000000000000000003b300000000000000000000000000000000000000000000000000000000000003b400000000000000000000000000000000000000000000000000000000000003b500000000000000000000000000000000000000000000000000000000000003b60200000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000481100000000000000000000000000000000000000000000000000000000000000680000000000000000000000000000000000000000000000000000000000000068a0000000000000000000000000000000000000000000000000000000000000681000000000000000000000000000000000000000000000000000000000000068b0000000000000000000000000000000000000000000000000000000000000682000000000000000000000000000000000000000000000000000000000000068c0000000000000000000000000000000000000000000000000000000000000683000000000000000000000000000000000000000000000000000000000000068d0000000000000000000000000000000000000000000000000000000000000684000000000000000000000000000000000000000000000000000000000000068e0000000000000000000000000000000000000000000000000000000000000685000000000000000000000000000000000000000000000000000000000000068f00000000000000000000000000000000000000000000000000000000000006860000000000000000000000000000000000000000000000000000000000000690000000000000000000000000000000000000000000000000000000000000068700000000000000000000000000000000000000000000000000000000000006910000000000000000000000000000000000000000000000000000000000000688000000000000000000000000000000000000000000000000000000000000069200000000000000000000000000000000000000000000000000000000000006890000000000000000000000000000000000000000000000000000000000000693000000000000000000000000000000000000000000000000000000000000068a0000000000000000000000000000000000000000000000000000000000000694000000000000000000000000000000000000000000000000000000000000068b0000000000000000000000000000000000000000000000000000000000000695000000000000000000000000000000000000000000000000000000000000068c0000000000000000000000000000000000000000000000000000000000000696000000000000000000000000000000000000000000000000000000000000068d0000000000000000000000000000000000000000000000000000000000000697000000000000000000000000000000000000000000000000000000000000068e0000000000000000000000000000000000000000000000000000000000000698000000000000000000000000000000000000000000000000000000000000068f0000000000000000000000000000000000000000000000000000000000000699000011000000021c000000b02266c57cf826945c39d839e2b438992a29c8af29e26ff78bb754a0c3e2450b0240420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb6109c77cc0380b6beffe6a4d8410d9a0d65ef7b784d5abda7a43802ba0240d625168a91b1aa62ab48bde1d6866771446c51e28cb37f0c7b9e55418cbd4cece7c4000000b009badea0b27cc6597974bae4cc45ea48b1610bce2f7d7ed53798d33d1cf5517d44420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb62854df629f0888e5f7d36b90da9c438915bbc0651421b582685e2ac72cf11ca12e42f94845ea7d6fb5ce9d3f00ffede801aed1a045d373791967b4ca779d2e40000000b0217346374e0498807161819d65d493c4612d50baf64476affbbefb4a47a597f948420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb60fa8f886595ebae3376fec92f2a994a79d541d09612f3ccbe8a25d4067a1631c1597126c0040af6cf56b1e41190d3f0689472e4492e0fac299abe743b24d74bb0000021c000000b008c75f5b085aca7db0fe029f7de1e4e2e8c5ad5f4351fdf97c032dc38255de744c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb62761601cf4e68d0a2f5cb34b8c383e234d2061f627f634a6acc8854d9251a9982d4f7a029bc88193ed57e4f9b29be8823913733159a7f29d5dd20f50dcfdbb37000000b0207fc6f1a3e29ca4a8eac95817708e5e9891f24c0a18f5d4402955d0ad0624f050420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb60eb57940af3cbf076ef9344da4458f41d4b8be9a7503bbf02d0cb7c6cd01f01314a39326561eb3912cf465fbcaa939a0c0abcfd5a6b579e6de1641ca17ae01b2000000b007d3e0155e38cea1e8874a5a2f7ddf7d202a4ef057267d1dc06d8849e7b66b6b54420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb6266de0d74ac4912e66e5fb063dd438bd848503873bcab3caf132dfd3f7b2368f2c5bfabcf1a685b824e12cb46437e31c707814c26d7c71c1a23c69d7425e482e0000021c000000b01f8c47abf9c0a0c8e0741112c90c88f8cff693dd1ded74f88493b0571266b1e758420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb60dc1f9fb051ac32ba6827c0855e189dc0c1d602b88d83b147177124d32627d0a13b013e0abfcb7b5647dadb67c45343af8107166ba89f90b22809c507d0e8ea9000000b006e060cfb416d2c620109214e119da17578ef0816afafc4204d7e2d04d16f8625c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb6257a6191a0a295529e6f42c0ef703357bbe9a5184f9f32ef359d3a5a5d12c3862b687b77478489dc5c6a746f15d3ddb6a7dcb6538150f0e5e6a6c45da7bed525000000b01e98c8664f9ea4ed17fd58cd7aa88393075b356e31c1f41cc8fe0add77c73ede60420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb60cce7ab55af8c74fde0bc3c3077d8476438201bc9cacba38b5e16cd397c30a0112bc949b01dabbd99c06f5712de12ed52f7512f7ce5e782f66eaf6d6e26f1ba00000021c000000b005ece18a09f4d6ea5799d9cf92b5d4b18ef392127ecf7b6649423d56b277855964420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb62486e24bf6809976d5f88a7ba10c2df1f34e46a96373b2137a0794e0c273507d2a74fc319d628e0093f3bc29c76fd850df4157e49525700a2b111ee40d1f621c000000b01da54920a57ca9114f86a0882c447e2d3ebfd6ff459673410d686563dd27cbd568420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb60bdafb6fb0d6cb7415950b7db9197f107ae6a34db081395cfa4bc759fd2396f811c9155557b8bffdd3903d2bdf7d296f66d9b488e232f753ab55515d47cfa897000000b004f962445fd2db0e8f23218a4451cf4bc65833a392a3fa8a8dac97dd17d812506c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb6239363064c5e9d9b0d81d23652a8288c2ab2e83a77483137be71ef6727d3dd7429817cebf3409224cb7d03e4790bd2eb16a5f975a8f9ef2e6f7b796a727fef130000021c000000b01cb1c9dafb5aad35870fe842dde078c776247890596af26551d2bfea428858cc70420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb60ae77c2a06b4cf984d1e53386ab579aab24b44dec455b8813eb621e0628423ef10d5960fad96c4220b1984e6911924099e3e5619f6077677efbfabe3ad30358e000000b00405e2feb5b0df32c6ac6944f5edc9e5fdbcd534a67879aed216f2637d389f4774420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb6229fe3c0a23ca1bf450b19f104442326621789cb8b1cb05c02dc49ed8d346a6b288dfda6491e964903064b9f2aa7cd854e0a9b06bcce6e52b3e5d3f0d7e07c0a000000b01bbe4a955138b159be992ffd8f7c7361ad891a216d3f7189963d1a70a7e8e5c378420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb609f3fce45c92d3bc84a79af31c517444e9afe66fd82a37a583207c66c7e4b0e60fe216ca0374c84642a2cca142b51ea3d5a2f7ab09dbf59c342a066a1290c2850000021c000000b0031263b90b8ee356fe35b0ffa789c480352176c5ba4cf8d316814ce9e2992c3e7c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb621ac647af81aa5e37c9461abb5e01dc0997c2b5c9ef12f804746a473f294f762279a7e609efc9a6d3a8f9359dc43c81f856f3c97d0a2ed76f8502e773d410901000000b01acacb4fa716b57df62277b841186dfbe4edbbb28113f0addaa774f70d4972ba80420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb609007d9eb270d7e0bc30e2adcded6edf21148800ebfeb6c9c78ad6ed2d453ddd0eee97845952cc6a7a2c145bf451193e0d07993c1db074c0789460f077f14f7c000000b0021ee473616ce77b35bef8ba5925bf1a6c861856ce2177f75aeba77047f9b93584420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb620b8e5354df8aa07b41da966677c185ad0e0ccedb2c5aea48bb0fefa57f5845926a6ff1af4da9e917218db148ddfc2b9bcd3de28e4776c9b3cba88fda2a195f80000021c000000b019d74c09fcf4b9a22dabbf72f2b468961c525d4394e86fd21f11cf7d72a9ffb188420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb6080cfe59084edc04f3ba2a687f89697958792991ffd335ee0bf5317392a5cad40dfb183eaf30d08eb1b55c16a5ed13d8446c3acd3184f3e4bcfebb76dd51dc73000000b0012b652db74aeb9f6d4840750ac1b9b4a3eab9e7e1f5f71b9f5601f6ad5a462c8c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb61fc565efa3d6ae2beba6f121191812f508456e7ec69a2dc8d01b5980bd56115025b37fd54ab8a2b5a9a222cf3f7bbd53f4387fb9f84bebbf8124e384080222ef000000b018e3ccc452d2bdc66535072da450633053b6fed4a8bceef6637c2a03d80a8ca890420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb607197f135e2ce0292b437223312564138fddcb2313a7b512505f8bf9f80657cb0d0798f9050ed4b2e93ea3d157890e727bd0dc5e45597309016915fd42b2696a0000021c000000b00037e5e80d28efc3a4d1882fbc5db44edb4f5b78f5ca763fe3c05c7d12bad32394420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb61ed1e6a9f9b4b250233038dbcab40d8f3faa100fda6eaced1485b40722b69e4724c0008fa096a6d9e12b6a89f117b7ee2b9d214b0c206ae3c58f3e0a6d62afe6000000b017f04d7ea8b0c1ea9cbe4ee855ec5dca8b1ba065bc916e1aa7e6848a3d6b199f98420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb60625ffcdb40ae44d62ccb9dde2c15eadc7426cb4277c343694c9e6805d66e4c20c1419b35aecd8d720c7eb8c0925090cb3357def592df22d45d37083a812f661000000b02fa8b5154438941194ab15a0ef7b07463ae7e552835865f56c0cac97681b601b9c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb61dde67644f92b6745ab980967c500829770eb1a0ee432c1158f00e8d88172b3e23cc8149f674aafe18b4b244a2b3b2886301c2dc1ff4ea0809f99890d2c33cdd00000fa400000168000000b016fcce38fe8ec60ed44796a307885864c28041f6d065ed3eec50df10a2cba696a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb60532808809e8e8719a560198945d5947fea70e453b50b35ad9344106c2c771b90b209a6db0cadcfb58513346bac103a6ea9a1f806d0271518a3dcb0a0d738358000000b02eb535cf9a169835cc345d5ba11701e0724c86e3972ce519b077071dcd7bed12a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb61ceae81ea570ba989242c8512dec02c3ae7353320217ab359d5a6913ed77b83522d902044c52af22503df9ff544fad229a66646d33c9692c4e63f3173823c9d400000168000000b016094ef3546cca330bd0de5db92452fef9e4e387e43a6c6330bb3997082c338da8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6043f01425fc6ec95d1df495345f953e2360bafd64f25327f1d9e9b8d2827feb00a2d1b2806a8e11f8fda7b016c5cfe4121fec11180d6f075cea8259072d4104f000000b02dc1b689eff49c5a03bda51652b2fc7aa9b12874ab01643df4e161a432dc7a09ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb61bf768d8fb4ebebcc9cc100bdf87fd5de5d7f4c315ec2a59e1c4c39a52d8452c21e582bea230b34687c741ba05eba7bcd1cb05fe479de85092ce4d9d9d8456cb00000168000000b01515cfadaa4ace57435a26186ac04d9931498518f80eeb877525941d6d8cc084b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6034b81fcb5a4f0ba0968910df7954e7c6d70516762f9b1a36208f6138d888ba709399be25c86e543c763c2bc1df8f8db596362a294ab6f9a13128016d8349d46000000b02cce374445d2a07e3b46ecd1044ef714e115ca05bed5e362394bbc2a983d0700b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb61b03e993512cc2e1015557c69123f7f81d3c965429c0a97e262f1e20b838d22320f20378f80eb76abf508974b787a257092fa78f5b726774d738a82402e4e3c200000168000000b0142250680028d27b7ae36dd31c5c483368ae26aa0be36aabb98feea3d2ed4d7bb8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6025802b70b82f4de40f1d8c8a9314916a4d4f2f876ce30c7a6735099f2e9189e08461c9cb264e967feed0a76cf94f37590c80433a87feebe577cda9d3d952a3d000000b02bdab7fe9bb0a4a272d0348bb5eaf1af187a6b96d2aa62867db616b0fd9d93f7bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb61a106a4da70ac70538de9f8142bff29254a137e53d9528a26a9978a71d995f1a1ffe84334decbb8ef6d9d12f69239cf1409449206f46e6991ba302aa684570b900000168000000b0132ed1225606d69fb26cb58dcdf842cda012c83b1fb7e9cffdfa492a384dda72c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6016483716160f902787b20835acd43b0dc3994898aa2afebeaddab205849a59507529d570842ed8c367652318130ee0fc82ca5c4bc546de29be73523a2f5b734000000b02ae738b8f18ea8c6aa597c466786ec494fdf0d27e67ee1aac220713762fe20eec4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6191ceb07fce8cb297067e73bf45bed2c8c05d9765169a7c6af03d32d82f9ec111f0b04eda3cabfb32e6318ea1abf978b77f8eab1831b65bd600d5d30cda5fdb000000168000000b0123b51dcabe4dac3e9f5fd487f943d67d77769cc338c68f44264a3b09dae6769c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb60071042bb73efd26b004683e0c693e4b139e361a9e772f102f4805a6bdaa328c065f1e115e20f1b06dff99ec32cce8a9ff914755d028ed06e0518faa0856442b000000b029f3b973476caceae1e2c4011922e6e38743aeb8fa5360cf068acbbdc85eade5cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb618296bc252c6cf4da7f12ef6a5f7e7c6c36a7b07653e26eaf36e2db3e85a79081e1785a7f9a8c3d765ec60a4cc5b9225af5d8c4296efe4e1a477b7b733068aa700000168000000b01147d29701c2dee8217f4503313038020edc0b5d4760e81886cefe37030ef460d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb62fe1d358ee4ea1749fddf5af3f8691427336bff42c051ec5b79455c1130abf84056b9ecbb3fef5d4a588e1a6e468e34436f5e8e6e3fd6c2b24bbea306db6d122000000b029003a2d9d4ab10f196c0bbbcabee17dbea8504a0e27dff34af526442dbf3adcd4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb61735ec7ca8a4d371df7a76b15793e260facf1c987912a60f37d8883a4dbb05ff1d2406624f86c7fb9d75a85f7df78cbfe6c22dd3aac46405e8e2123d9867179e00000168000000b01054535157a0e30c59088cbde2cc329c4640acee5b35673ccb3958bd686f8157d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb62eee5413442ca598d7673d69f1228bdcaa9b61853fd99de9fbfeb047786b4c7b04781f8609dcf9f8dd1229619604ddde6e5a8a77f7d1eb4f692644b6d3175e19000000b0280cbae7f328b53350f553767c5adc17f60cf1db21fc5f178f5f80ca931fc7d3dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb616426d36fe82d7961703be6c092fdcfb3233be298ce725337c42e2c0b31b92f61c30871ca564cc1fd4fef01a2f93875a1e26cf64be98e32a2d4c6cc3fdc7a49500000168000000b00f60d40bad7ee7309091d47894682d367da54e7f6f09e6610fa3b343cdd00e4ee0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb62dfad4cd9a0aa9bd0ef08524a2be8676e200031653ae1d0e40690acdddcbd9720384a0405fbafe1d149b711c47a0d878a5bf2c090ba66a73ad909f3d3877eb10000000b027193ba24906b957887e9b312df6d6b22d71936c35d0de3bd3c9db50f88054cae4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6154eedf15460dbba4e8d0626bacbd79569985fbaa0bba457c0ad3d47187c1fed1b3d07d6fb42d0440c8837d4e12f81f4558b70f5d26d624e71b6c74a6328318c00000168000000b00e6d54c6035ceb54c81b1c33460427d0b509f01082de6585540e0dca33309b45e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb62d075587efe8ade14679ccdf545a81111964a4a767829c3284d36554432c6669029120fab59902414c24b8d6f93cd312dd23cd9a1f7ae997f1faf9c39dd87807000000b02625bc5c9ee4bd7bc007e2ebdf92d14c64d634fd49a55d60183435d75de0e1c1ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6145b6eabaa3edfde86164de16c67d22fa0fd014bb490237c051797cd7ddcace41a4988915120d46844117f8f92cb7c8e8cf01286e641e172b62121d0c888be8300000168000000b00d79d580593aef78ffa463edf7a0226aec6e91a196b2e4a9987868509891283cf0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb62c13d64245c6b2057e03149a05f67bab50c946387b571b56c93dbfdaa88cf360019da1b50b77066583ae0091aad8cdad14886f2b334f68bc3665544a033904fe000000b025323d16f4c2c19ff7912aa6912ecbe69c3ad68e5d79dc845c9e905dc3416eb8f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb61367ef66001ce402bd9f959c1e03ccc9d861a2dcc864a2a04981f253e33d39db1956094ba6fed88c7b9ac74a44677728c454b417fa166096fa8b7c572de94b7a3800000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002c100000000000000000000000000000000000000000000000000000000000002c200000000000000000000000000000000000000000000000000000000000002c300000000000000000000000000000000000000000000000000000000000002c400000000000000000000000000000000000000000000000000000000000002c500000000000000000000000000000000000000000000000000000000000002c600000000000000000000000000000000000000000000000000000000000002c700000000000000000000000000000000000000000000000000000000000002c800000000000000000000000000000000000000000000000000000000000002c900000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000002cb00000000000000000000000000000000000000000000000000000000000002cc00000000000000000000000000000000000000000000000000000000000002cd00000000000000000000000000000000000000000000000000000000000002ce00000000000000000000000000000000000000000000000000000000000002cf00000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002d100000000000000000000000000000000000000000000000000000000000002d200000000000000000000000000000000000000000000000000000000000002d300000000000000000000000000000000000000000000000000000000000002d400000000000000000000000000000000000000000000000000000000000002d500000000000000000000000000000000000000000000000000000000000002d600000000000000000000000000000000000000000000000000000000000002d700000000000000000000000000000000000000000000000000000000000002d800000000000000000000000000000000000000000000000000000000000002d900000000000000000000000000000000000000000000000000000000000002da00000000000000000000000000000000000000000000000000000000000002db00000000000000000000000000000000000000000000000000000000000002dc00000000000000000000000000000000000000000000000000000000000002dd00000000000000000000000000000000000000000000000000000000000002de00000000000000000000000000000000000000000000000000000000000002df00000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002e100000000000000000000000000000000000000000000000000000000000002e200000000000000000000000000000000000000000000000000000000000002e300000000000000000000000000000000000000000000000000000000000002e400000000000000000000000000000000000000000000000000000000000002e500000000000000000000000000000000000000000000000000000000000002e600000000000000000000000000000000000000000000000000000000000002e700000000000000000000000000000000000000000000000000000000000002e800000000000000000000000000000000000000000000000000000000000002e900000000000000000000000000000000000000000000000000000000000002ea00000000000000000000000000000000000000000000000000000000000002eb00000000000000000000000000000000000000000000000000000000000002ec00000000000000000000000000000000000000000000000000000000000002ed00000000000000000000000000000000000000000000000000000000000002ee00000000000000000000000000000000000000000000000000000000000002ef00000000000000000000000000000000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000002f100000000000000000000000000000000000000000000000000000000000002f200000000000000000000000000000000000000000000000000000000000002f300000000000000000000000000000000000000000000000000000000000002f400000000000000000000000000000000000000000000000000000000000002f500000000000000000000000000000000000000000000000000000000000002f600000000000000000000000000000000000000000000000000000000000002f73700000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c100000000000000000000000000000000000000000000000000000000000003c200000000000000000000000000000000000000000000000000000000000003c300000000000000000000000000000000000000000000000000000000000003c400000000000000000000000000000000000000000000000000000000000003c500000000000000000000000000000000000000000000000000000000000003c600000000000000000000000000000000000000000000000000000000000003c700000000000000000000000000000000000000000000000000000000000003c800000000000000000000000000000000000000000000000000000000000003c900000000000000000000000000000000000000000000000000000000000003ca00000000000000000000000000000000000000000000000000000000000003cb00000000000000000000000000000000000000000000000000000000000003cc00000000000000000000000000000000000000000000000000000000000003cd00000000000000000000000000000000000000000000000000000000000003ce00000000000000000000000000000000000000000000000000000000000003cf00000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d100000000000000000000000000000000000000000000000000000000000003d200000000000000000000000000000000000000000000000000000000000003d300000000000000000000000000000000000000000000000000000000000003d400000000000000000000000000000000000000000000000000000000000003d500000000000000000000000000000000000000000000000000000000000003d600000000000000000000000000000000000000000000000000000000000003d700000000000000000000000000000000000000000000000000000000000003d800000000000000000000000000000000000000000000000000000000000003d900000000000000000000000000000000000000000000000000000000000003da00000000000000000000000000000000000000000000000000000000000003db00000000000000000000000000000000000000000000000000000000000003dc00000000000000000000000000000000000000000000000000000000000003dd00000000000000000000000000000000000000000000000000000000000003de00000000000000000000000000000000000000000000000000000000000003df00000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e100000000000000000000000000000000000000000000000000000000000003e200000000000000000000000000000000000000000000000000000000000003e300000000000000000000000000000000000000000000000000000000000003e400000000000000000000000000000000000000000000000000000000000003e500000000000000000000000000000000000000000000000000000000000003e600000000000000000000000000000000000000000000000000000000000003e700000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000003ea00000000000000000000000000000000000000000000000000000000000003eb00000000000000000000000000000000000000000000000000000000000003ec00000000000000000000000000000000000000000000000000000000000003ed00000000000000000000000000000000000000000000000000000000000003ee00000000000000000000000000000000000000000000000000000000000003ef00000000000000000000000000000000000000000000000000000000000003f000000000000000000000000000000000000000000000000000000000000003f100000000000000000000000000000000000000000000000000000000000003f200000000000000000000000000000000000000000000000000000000000003f300000000000000000000000000000000000000000000000000000000000003f400000000000000000000000000000000000000000000000000000000000003f500000000000000000000000000000000000000000000000000000000000003f60200000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000004c11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000006ca00000000000000000000000000000000000000000000000000000000000006c100000000000000000000000000000000000000000000000000000000000006cb00000000000000000000000000000000000000000000000000000000000006c200000000000000000000000000000000000000000000000000000000000006cc00000000000000000000000000000000000000000000000000000000000006c300000000000000000000000000000000000000000000000000000000000006cd00000000000000000000000000000000000000000000000000000000000006c400000000000000000000000000000000000000000000000000000000000006ce00000000000000000000000000000000000000000000000000000000000006c500000000000000000000000000000000000000000000000000000000000006cf00000000000000000000000000000000000000000000000000000000000006c600000000000000000000000000000000000000000000000000000000000006d000000000000000000000000000000000000000000000000000000000000006c700000000000000000000000000000000000000000000000000000000000006d100000000000000000000000000000000000000000000000000000000000006c800000000000000000000000000000000000000000000000000000000000006d200000000000000000000000000000000000000000000000000000000000006c900000000000000000000000000000000000000000000000000000000000006d300000000000000000000000000000000000000000000000000000000000006ca00000000000000000000000000000000000000000000000000000000000006d400000000000000000000000000000000000000000000000000000000000006cb00000000000000000000000000000000000000000000000000000000000006d500000000000000000000000000000000000000000000000000000000000006cc00000000000000000000000000000000000000000000000000000000000006d600000000000000000000000000000000000000000000000000000000000006cd00000000000000000000000000000000000000000000000000000000000006d700000000000000000000000000000000000000000000000000000000000006ce00000000000000000000000000000000000000000000000000000000000006d800000000000000000000000000000000000000000000000000000000000006cf00000000000000000000000000000000000000000000000000000000000006d9000011000000021c000000b00c86563aaf18f39d372daba8a93c1d0523d33332aa8763cddce2c2d6fdf1b533f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb62b2056fc9ba4b629b58c5c54b7927645882de7c98f2b9a7b0da81a610ded805700aa226f61550a89bb37484c5c74c8474bed10bc4723e7e07acfaed0689991f5000000b0243ebdd14aa0c5c42f1a726142cac680d39f781f714e5ba8a108eae428a1fbaffc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb61274702055fae826f528dd56cf9fc7640fc6446ddc3921c48dec4cda489dc6d218628a05fcdcdcb0b3240f04f60371c2fbb955a90deadfbb3ef5d6dd9349d871000000b00bfa610ad4ff82b51dd21bf27b0fd220f197586d7b63833d51897e93d87b4f5b00430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb62a2cd7b6f182ba4ded15a40f692e70dfbf92895aa300199f521274e7734e0d4e301af19c9864aed7ab10d5bd8f921b3eab859a95d4b1d796031bfeeabdfa1eed0000021c000000b023b2c8a1708754dc15bee2ab149e7b9ca1639d5a422a7b1815afa6a1032b95d704430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb611e87af07be1773edbcd4da0a1737c7fdd8a69a8ad15413402930897232760fa17d694d622c36bc899c87f4ec7d726dec97d7ae3dec6ff2ab39c929a6dd37299000000b00b06e1c52add86d9555b63ad2cabccbb28fbf9fe8f38026195f3d91a3ddbdc5208430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb629a0e28717694965d3ba14593b0225fb8d56ae9573dc390ec6b930a44dd7a7762f8efc6cbe4b3def91b546076165d05a7949bfd0a58df70577c2baa79883b915000000b022bf495bc66559004d482a65c63a7636d8c83eeb55fefa3c5a1a0127688c22ce0c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb610f4fbaad1bf7b631356955b530f771a14ef0b39c0e9c05846fd631d8887edf116e3159078a16fecd151c7097973217900e21c74f29b7e4ef806ed20d333ff900000021c000000b00a13627f80bb8afd8ce4ab67de47c75560609b8fa30c8185da5e33a0a33c694910430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb628ad63416d474d8a0b435c13ec9e2095c4bb502687b0b8330b238b2ab338346d2e9b7d2714294213c93e8dc21301caf4b0ae6161b9627629bc2d152dfde4460c000000b021cbca161c435d2484d1722077d670d1102ce07c69d379609e845badcdecafc514430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb610017c65279d7f874adfdd1604ab71b44c53accad4be3f7c8b67bda3ede87ae815ef964ace7f741108db0ec42b0f1c133846be06066ffd733c7147a738948c87000000b0091fe339d6998f21c46df3228fe3c1ef97c53d20b6e100aa1ec88e27089cf64018430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb627b9e3fbc32551ae42cca3ce9e3a1b2ffc1ff1b79b8537574f8de5b11898c1642da7fde16a07463800c7d57cc49dc58ee81302f2cd36f54e00976fb46344d3030000021c000000b020d84ad072216148bc5ab9db29726b6b4791820d7da7f884e2eeb634334d3cbc1c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb60f0dfd1f7d7b83ab826924d0b6476c4e83b84e5be892bea0cfd2182a534907df14fc1705245d78354064567edcab16ad6fab5f971a447c9780dba22d9df5197e000000b0082c63f42c779345fbf73add417fbc89cf29deb1cab57fce6332e8ad6dfd833720430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb626c664b6190355d27a55eb894fd615ca33849348af59b67b93f840377df94e5b2cb47e9bbfe54a5c38511d377639c0291f77a483e10b74724501ca3ac8a55ffa000000b01fe4cb8ac7ff656cf3e40195db0e66057ef6239e917c77a9275910ba98adc9b324430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb60e1a7dd9d35987cfb9f26c8b67e366e8bb1cefecfc673dc5143c72b0b8a994d6140897bf7a3b7c5977ed9e398e471147a71001282e18fbbbc545fcb40355a6750000021c000000b00738e4ae8255976a33808297f31bb724068e8042de89fef2a79d4333d35e102e28430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb625d2e5706ee159f6b1df3344017210646ae934d9c32e359fd8629abde359db522bc0ff5615c34e806fda64f227d5bac356dc4614f4dff396896c24c12e05ecf1000000b01ef14c451ddd69912b6d49508caa609fb65ac52fa550f6cd6bc36b40fe0e56aa2c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb60d26fe9429378bf3f17bb446197f6182f281917e103bbce958a6cd371e0a21cd13151879d019807daf76e5f43fe30be1de74a2b941ed7ae009b0573a68b6336c000000b006456568d8339b8e6b09ca52a4b7b1be3df321d3f25e7e16ec079dba38be9d2530430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb624df662ac4bf5e1ae9687afeb30e0afea24dd66ad702b4c41cccf54448ba68492acd80106ba152a4a763acacd971b55d8e40e7a608b472bacdd67f47936679e80000021c000000b01dfdccff73bb6db562f6910b3e465b39edbf66c0b92575f1b02dc5c7636ee3a134430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb60c337f4e7f1590182904fc00cb1b5c1d29e6330f24103c0d9d1127bd836aaec41221993425f784a1e7002daef17f067c15d9444a55c1fa044e1ab1c0ce16c063000000b00551e6232e119fb2a293120d5653ac587557c3650632fd3b3071f8409e1f2a1c38430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb623ebe6e51a9d623f20f1c2b964aa0598d9b277fbead733e861374fcaae1af54029da00cac17f56c8deecf4678b0daff7c5a589371c88f1df1240d9cdf8c706df000000b01d0a4db9c99971d99a7fd8c5efe255d425240851ccf9f515f498204dc8cf70983c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb60b400008d4f3943c608e43bb7cb756b7614ad4a037e4bb31e17b8243e8cb3bbb112e19ee7bd588c61e897569a31b01164d3de5db6996792892850c4733774d5a0000021c000000b0045e66dd83efa3d6da1c59c807efa6f2acbc64f61a077c5f74dc52c7037fb71340430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb622f8679f707b6663587b0a74164600331117198cfeabb30ca5a1aa51137b823728e68185175d5aed16763c223ca9aa91fd0a2ac8305d710356ab34545e2793d6000000b01c16ce741f7775fdd2092080a17e506e5c88a9e2e0ce743a39027ad42e2ffd8f44430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb60a4c80c32ad1986098178b762e53515198af76314bb93a5625e5dcca4e2bc8b2103a9aa8d1b38cea5612bd2454b6fbb084a2876c7d6af84cd6ef66cd98d7da51000000b0036ae797d9cda7fb11a5a182b98ba18ce42106872ddbfb83b946ad4d68e0440a48430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb62204e859c6596a879004522ec7e1facd487bbb1e12803230ea0c04d778dc0f2e27f3023f6d3b5f114dff83dcee45a52c346ecc594431f0279b158edac38820cd0000021c000000b01b234f2e75557a220992683b531a4b0893ed4b73f4a2f35e7d6cd55a93908a864c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb60959017d80af9c84cfa0d330dfef4bebd01417c25f8db97a6a503750b38c55a90f471b632791910e8d9c04df0652f64abc0728fd913f77711b59c153fe386748000000b0027768522fabac1f492ee93d6b279c271b85a81841b07aa7fdb107d3ce40d10150430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb6211169141c376eabc78d99e9797df5677fe05caf2654b1552e765f5dde3c9c2526ff82f9c31963358588cb979fe19fc66bd36dea58066f4bdf7fe96128e8adc4000000b01a2fcfe8cb337e46411baff604b645a2cb51ed0508777282c1d72fe0f8f1177d54430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb608658237d68da0a9072a1aeb918b46860778b9537362389eaeba91d718ece2a00e539c1d7d6f9532c5254c99b7eef0e4f36bca8ea513f6955fc41bda6398f43f00000fa400000168000000b00183e90c8589b04380b830f81cc396c152ea49a95584f9cc421b625a33a15df858430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb6201de9ce721572cfff16e1a42b19f001b744fe403a29307972e0b9e4439d291c260c03b418f76759bd121352517d9a60a3380f7b6bdaee7023ea43e78e493abb000000b0193c50a32111826a78a4f7b0b652403d02b68e961c4bf1a706418a675e51a4745c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb6077202f22c6ba4cd3eb362a6432741203edd5ae48736b7c2f324ec5d7e4d6f970d601cd7d34d9956fcae9454698aeb7f2ad06c1fb8e875b9a42e7660c8f9813600000168000000b0009069c6db67b467b84178b2ce5f915b8a4eeb3a695978f08685bce09901eaef60430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb61f2a6a88c7f376f436a0295edcb5ea9beea99fd14dfdaf9db74b146aa8fdb6132518846e6ed56b7df49b5b0d031994fada9cb10c7faf6d9468549e6df3a9c7b2000000b01848d15d76ef868eb02e3f6b67ee3ad73a1b3027302070cb4aabe4edc3b2316b64430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb6067e83ac8249a8f1763caa60f4c33bba7641fc759b0b36e7378f46e3e3adfc8e0c6c9d92292b9d7b3437dc0f1b26e61962350db0ccbcf4dde898d0e72e5a0e2d00000168000000b0300138f4127758b5a81b0624017ce452e9e77513f6e768a60ed20cfaee6277e768430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb61e36eb431dd17b186e2971198e51e536260e416261d22ec1fbb56ef10e5e430a24250528c4b36fa22c24a2c7b4b58f951201529d9383ecb8acbef8f4590a54a9000000b017555217cccd8ab2e7b78726198a3571717fd1b843f4efef8f163f742912be626c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb6058b0466d827ad15adc5f21ba65f3654ada69e06aedfb60b7bf9a16a490e89850b791e4c7f09a19f6bc123c9ccc2e0b39999af41e09174022d032b6d93ba9b2400000168000000b02f0db9ae68555cd9dfa44ddeb318deed214c16a50abbe7ca533c678153c304de70430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb61d436bfd73af7f3ca5b2b8d43feddfd05d72e2f375a6ade6401fc97773bed001233185e31a9173c663adea8266518a2f4965f42ea7586bdcf129537abe6ae1a0000000b01661d2d222ab8ed71f40cee0cb26300ba8e4734957c96f13d38099fa8e734b5974430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb6049785212e05b139e54f39d657fb30eee50b3f97c2b4352fc063fbf0ae6f167c0a859f06d4e7a5c3a34a6b847e5edb4dd0fe50d2f465f326716d85f3f91b281b00000168000000b02e1a3a68be3360fe172d959964b4d98758b0b8361e9066ee97a6c207b92391d578430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb61c4fecb7c98d8360dd3c008ef189da6a94d78484897b2d0a848a23fdd91f5cf8223e069d706f77ea9b37323d17ed84c980ca95bfbb2ceb013593ae0123cb6e97000000b0156e538c788992fb56ca169b7cc22aa5e04914da6b9dee3817eaf480f3d3d8507c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb603a405db83e3b55e1cd8819109972b891c6fe128d688b45404ce567713cfa37309921fc12ac5a9e7dad3b33f2ffad5e80862f264083a724ab5d7e07a5e7bb51200000168000000b02d26bb23141165224eb6dd541650d421901559c73264e612dc111c8e1e841ecc80430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb61b5c6d721f6b878514c54849a325d504cc3c26159d4fac2ec8f47e843e7fe9ef214a8757c64d7c0ed2c079f7c9897f63b82f3750cf016a2579fe0887892bfb8e000000b0147ad446ce67971f8e535e562e5e254017adb66b7f726d5c5c554f075934654784430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb602b08695d9c1b9825461c94bbb33262353d482b9ea5d33784938b0fd7930306a089ea07b80a3ae0c125cfaf9e196d0823fc793f51c0ef16efa423b00c3dc420900000168000000b02c333bdd69ef69468640250ec7eccebbc779fb5846396537207b771483e4abc388430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb61a68ee2c75498ba94c4e900454c1cf9f03a0c7a6b1242b530d5ed90aa3e076e6205708121c2b80330a49c1b27b2579fdef93d8e1e2d5e949be68630dee8c8885000000b01387550124459b43c5dca610dffa1fda4f1257fc9346ec80a0bfa98dbe94f23e8c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb601bd07502f9fbda68beb11066ccf20bd8b39244afe31b29c8da30b83de90bd6107ab2135d681b23049e642b49332cb1c772c35862fe370933eac9587293ccf0000000168000000b02b3fbc97bfcd6d6abdc96cc97988c955fede9ce95a0de45b64e5d19ae94538ba90430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb619756ee6cb278fcd83d7d7bf065dca393b056937c4f8aa7751c93391094103dd1f6388cc7209845741d3096d2cc1749826f87a72f6aa686e02d2bd9453ed157c000000b01293d5bb7a239f67fd65edcb91961a748676f98da71b6ba4e52a041423f57f3594430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb600c9880a857dc1cac37458c11e6b1b57c29dc5dc120631c0d20d660a43f14a5806b7a1f02c5fb654816f8a6f44cec5b6ae90d71743b7efb78316f00d8e9d5bf700000168000000b02a4c3d5215ab718ef552b4842b24c3f036433e7a6de2637fa9502c214ea5c5b198430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb61881efa1210593f1bb611f79b7f9c4d3726a0ac8d8cd299b96338e176ea190d41e700986c7e7887b795c5127de5d6f325e5d1c040a7ee792473d181ab94da273000000b011a05675d001a38c34ef35864332150ebddb9b1ebaefeac929945e9a89560c2c9c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb6303a5737bc8d6618b34de63251886e4f22364fb59f9421765a59b6249951d75005c422aa823dba78b8f8d229f66ac050e5f578a8578c6edbc7814a93f3fde8ee00000168000000b02958be0c6b8975b32cdbfc3edcc0be8a6da7e00b81b6e2a3edba86a7b40652a8a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6178e705b76e39815f2ea67346995bf6da9ceac59eca1a8bfda9de89dd4021dcb1d7c8a411dc58c9fb0e598e28ff969cc95c1bd951e5366b68ba772a11eae2f6a000000b010acd73025dfa7b06c787d40f4ce0fa8f5403cafcec469ed6dfeb920eeb69923a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb62f46d7f2126b6a3cead72ded032468e9599af146b368a09a9ec410aafeb2644704d0a364d81bbe9cf08219e4a806baeb1d5a1a396b60ee000beba51a595e75e500000168000000b028653ec6c16779d7646543f98e5cb924a50c819c958b61c83224e12e1966df9fa8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6169af115ccc19c3a2a73aeef1b31ba07e1334deb007627e41f0843243962aac21c890afb73a390c3e86ee09d41956466cd265f263227e5dad011cd27840ebc61000000b00fb957ea7bbdabd4a401c4fba66a0a432ca4de40e298e911b26913a75417261aac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb62e5358ac68496e61226075a7b4c0638390ff92d7c73d1fbee32e6b316412f13e03dd241f2df9c2c1280b619f59a2b58554bebbca7f356d245055ffa0bebf02dc380000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030100000000000000000000000000000000000000000000000000000000000003020000000000000000000000000000000000000000000000000000000000000303000000000000000000000000000000000000000000000000000000000000030400000000000000000000000000000000000000000000000000000000000003050000000000000000000000000000000000000000000000000000000000000306000000000000000000000000000000000000000000000000000000000000030700000000000000000000000000000000000000000000000000000000000003080000000000000000000000000000000000000000000000000000000000000309000000000000000000000000000000000000000000000000000000000000030a000000000000000000000000000000000000000000000000000000000000030b000000000000000000000000000000000000000000000000000000000000030c000000000000000000000000000000000000000000000000000000000000030d000000000000000000000000000000000000000000000000000000000000030e000000000000000000000000000000000000000000000000000000000000030f0000000000000000000000000000000000000000000000000000000000000310000000000000000000000000000000000000000000000000000000000000031100000000000000000000000000000000000000000000000000000000000003120000000000000000000000000000000000000000000000000000000000000313000000000000000000000000000000000000000000000000000000000000031400000000000000000000000000000000000000000000000000000000000003150000000000000000000000000000000000000000000000000000000000000316000000000000000000000000000000000000000000000000000000000000031700000000000000000000000000000000000000000000000000000000000003180000000000000000000000000000000000000000000000000000000000000319000000000000000000000000000000000000000000000000000000000000031a000000000000000000000000000000000000000000000000000000000000031b000000000000000000000000000000000000000000000000000000000000031c000000000000000000000000000000000000000000000000000000000000031d000000000000000000000000000000000000000000000000000000000000031e000000000000000000000000000000000000000000000000000000000000031f0000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032100000000000000000000000000000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000323000000000000000000000000000000000000000000000000000000000000032400000000000000000000000000000000000000000000000000000000000003250000000000000000000000000000000000000000000000000000000000000326000000000000000000000000000000000000000000000000000000000000032700000000000000000000000000000000000000000000000000000000000003280000000000000000000000000000000000000000000000000000000000000329000000000000000000000000000000000000000000000000000000000000032a000000000000000000000000000000000000000000000000000000000000032b000000000000000000000000000000000000000000000000000000000000032c000000000000000000000000000000000000000000000000000000000000032d000000000000000000000000000000000000000000000000000000000000032e000000000000000000000000000000000000000000000000000000000000032f00000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000331000000000000000000000000000000000000000000000000000000000000033200000000000000000000000000000000000000000000000000000000000003330000000000000000000000000000000000000000000000000000000000000334000000000000000000000000000000000000000000000000000000000000033500000000000000000000000000000000000000000000000000000000000003360000000000000000000000000000000000000000000000000000000000000337370000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040100000000000000000000000000000000000000000000000000000000000004020000000000000000000000000000000000000000000000000000000000000403000000000000000000000000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000000004050000000000000000000000000000000000000000000000000000000000000406000000000000000000000000000000000000000000000000000000000000040700000000000000000000000000000000000000000000000000000000000004080000000000000000000000000000000000000000000000000000000000000409000000000000000000000000000000000000000000000000000000000000040a000000000000000000000000000000000000000000000000000000000000040b000000000000000000000000000000000000000000000000000000000000040c000000000000000000000000000000000000000000000000000000000000040d000000000000000000000000000000000000000000000000000000000000040e000000000000000000000000000000000000000000000000000000000000040f0000000000000000000000000000000000000000000000000000000000000410000000000000000000000000000000000000000000000000000000000000041100000000000000000000000000000000000000000000000000000000000004120000000000000000000000000000000000000000000000000000000000000413000000000000000000000000000000000000000000000000000000000000041400000000000000000000000000000000000000000000000000000000000004150000000000000000000000000000000000000000000000000000000000000416000000000000000000000000000000000000000000000000000000000000041700000000000000000000000000000000000000000000000000000000000004180000000000000000000000000000000000000000000000000000000000000419000000000000000000000000000000000000000000000000000000000000041a000000000000000000000000000000000000000000000000000000000000041b000000000000000000000000000000000000000000000000000000000000041c000000000000000000000000000000000000000000000000000000000000041d000000000000000000000000000000000000000000000000000000000000041e000000000000000000000000000000000000000000000000000000000000041f0000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000042100000000000000000000000000000000000000000000000000000000000004220000000000000000000000000000000000000000000000000000000000000423000000000000000000000000000000000000000000000000000000000000042400000000000000000000000000000000000000000000000000000000000004250000000000000000000000000000000000000000000000000000000000000426000000000000000000000000000000000000000000000000000000000000042700000000000000000000000000000000000000000000000000000000000004280000000000000000000000000000000000000000000000000000000000000429000000000000000000000000000000000000000000000000000000000000042a000000000000000000000000000000000000000000000000000000000000042b000000000000000000000000000000000000000000000000000000000000042c000000000000000000000000000000000000000000000000000000000000042d000000000000000000000000000000000000000000000000000000000000042e000000000000000000000000000000000000000000000000000000000000042f00000000000000000000000000000000000000000000000000000000000004300000000000000000000000000000000000000000000000000000000000000431000000000000000000000000000000000000000000000000000000000000043200000000000000000000000000000000000000000000000000000000000004330000000000000000000000000000000000000000000000000000000000000434000000000000000000000000000000000000000000000000000000000000043500000000000000000000000000000000000000000000000000000000000004360200000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000501100000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000070a0000000000000000000000000000000000000000000000000000000000000701000000000000000000000000000000000000000000000000000000000000070b0000000000000000000000000000000000000000000000000000000000000702000000000000000000000000000000000000000000000000000000000000070c0000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000000000000000000000000000000000070d0000000000000000000000000000000000000000000000000000000000000704000000000000000000000000000000000000000000000000000000000000070e0000000000000000000000000000000000000000000000000000000000000705000000000000000000000000000000000000000000000000000000000000070f00000000000000000000000000000000000000000000000000000000000007060000000000000000000000000000000000000000000000000000000000000710000000000000000000000000000000000000000000000000000000000000070700000000000000000000000000000000000000000000000000000000000007110000000000000000000000000000000000000000000000000000000000000708000000000000000000000000000000000000000000000000000000000000071200000000000000000000000000000000000000000000000000000000000007090000000000000000000000000000000000000000000000000000000000000713000000000000000000000000000000000000000000000000000000000000070a0000000000000000000000000000000000000000000000000000000000000714000000000000000000000000000000000000000000000000000000000000070b0000000000000000000000000000000000000000000000000000000000000715000000000000000000000000000000000000000000000000000000000000070c0000000000000000000000000000000000000000000000000000000000000716000000000000000000000000000000000000000000000000000000000000070d0000000000000000000000000000000000000000000000000000000000000717000000000000000000000000000000000000000000000000000000000000070e0000000000000000000000000000000000000000000000000000000000000718000000000000000000000000000000000000000000000000000000000000070f0000000000000000000000000000000000000000000000000000000000000719000011000000021c000000b02771bf8117457dfb9bee8bb43ff8b3bedc71232da95fe0ec768f3bb47ec76c96b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb615a771d0229fa05e61fcf6a9cccdb4a21897ef7c144aa70863729daa9ec337b91b958bb5c98194e81ff82857f3315f01048b00b745fc64ff147c27ade96f4958000000b00ec5d8a4d19baff8db8b0cb6580604dd64097fd1f66d6835f6d36e2db977b311b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb62d5fd966be27728559e9bd62665c5e1dc8643468db119ee32798c5b7c9737e3502e9a4d983d7c6e55f94a95a0b3eb01f8c235d5b9309ec4894c05a27241f8fd3000000b0267e403b6d23821fd377d36ef194ae5913d5c4bebd346010baf9963ae427f98db8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb614b3f28a787da48299863e647e69af3c4ffc910d281f262ca7dcf8310423c4b01aa20c701f5f990c57817012a4cd599b3befa24859d0e42358e682344ecfd64f0000021c000000b00dd2595f2779b41d1314547109a1ff779b6e21630a41e75a3b3dc8b41ed84008bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb62c6c5a21140576a99173051d17f858b7ffc8d5f9eee61e076c03203e2ed40b2c01f62593d9b5cb09971df114bcdaaab9c387feeca6de6b6cd92ab4ad89801cca000000b0258ac0f5c30186440b011b29a330a8f34b3a664fd108df34ff63f0c149888684c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb613c07344ce5ba8a6d10f861f3005a9d68761329e3bf3a550ec4752b7698451a719ae8d2a753d9d308f0ab7cd56695435735443d96da563479d50dcbab4306346000000b00cdeda197d57b8414a9d9c2bbb3dfa11d2d2c2f41e16667e7fa8233a8438ccffc4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb62b78dadb69e37acdc8fc4cd7c9945352372d778b02ba9d2bb06d7ac4943498230102a64e2f93cf2dcea738cf6e76a553faeca07dbab2ea911d950f33eee0a9c10000021c000000b0249741b018df8a68428a62e454cca38d829f07e0e4dd5e5943ce4b47aee9137bc8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb612ccf3ff2439accb0898cdd9e1a1a470bec5d42f4fc8247530b1ad3dcee4de9e18bb0de4cb1ba154c693ff8808054ecfaab8e56a8179e26be1bb37411990f03d000000b00beb5ad3d335bc658226e3e66cd9f4ac0a37648531eae5a2c4127dc0e99959f6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb62a855b95bfc17ef2008594927b304dec6e92191c168f1c4ff4d7d54af995251a000f27088571d3520630808a20129fee3251420ece8769b561ff69ba544136b8000000b023a3c26a6ebd8e8c7a13aa9f06689e27ba03a971f8b1dd7d8838a5ce1449a072d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb611d974b97a17b0ef40221594933d9f0af62a75c0639ca399751c07c434456b9517c78e9f20f9a578fe1d4742b9a14969e21d86fb954e6190262591c77ef17d340000021c000000b00af7db8e2913c089b9b02ba11e75ef46419c061645bf64c7087cd8474ef9e6edd4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb62991dc50159f8316380edc4d2ccc4886a5f6baad2a639b7439422fd15ef5b2112f7ff635bc81779ff60a0dfb532ff2e591e9cbe85c15596aea4bb9d4a9a1c3b0000000b022b04324c49b92b0b19cf259b80498c1f1684b030c865ca1cca3005479aa2d69d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb610e5f573cff5b51377ab5d4f44d999a52d8f1751777122bdb986624a99a5f88c16d40f5976d7a99d35a68efd6b3d44041982288ca922e0b46a8fec4de4520a2b000000b00a045c487ef1c4adf139735bd011e9e07900a7a75993e3eb4ce732cdb45a73e4dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6289e5d0a6b7d873a6f982407de684320dd5b5c3e3e381a987dac8a57c4563f082e8c76f0125f7bc42d9355b604cbed7fc94e6d796fe9d88f2eb6145b0f0250a70000021c000000b021bcc3df1a7996d4e9263a1469a0935c28ccec94205adbc6110d5adadf0aba60e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb60ff2762e25d3b937af34a509f675943f64f3b8e28b45a1e1fdf0bcd0ff06858315e09013ccb5adc16d2fd6b81cd93e9e50e6ca1dbcf75fd8aefa46d449b29722000000b00910dd02d4cfc8d228c2bb1681ade47ab06549386d68630f91518d5419bb00dbe4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb627aaddc4c15b8b5ea7216bc290043dbb14bffdcf520c99bcc216e4de29b6cbff2d98f7aa683d7fe8651c9d70b667e81a00b30f0a83be57b373206ee17462dd9e000000b020c9449970579af920af81cf1b3c8df660318e25342f5aea5577b561446b4757e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb60efef6e87bb1bd5be6bdecc4a8118ed99c585a739f1a2106425b17576467127a14ed10ce2293b1e5a4b91e72ce753938884b6baed0cbdefcf364a15aaf1324190000021c000000b0081d5dbd2aadccf6604c02d13349df14e7c9eac9813ce233d5bbe7da7f1b8dd2ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb626b75e7f17398f82deaab37d41a038554c249f6065e118e106813f648f1758f62ca57864be1b840c9ca5e52b6803e2b43817b09b9792d6d7b78ac967d9c36a95000000b01fd5c553c6359f1d5838c989ccd8889097962fb64803da0e99e20fe7a9cbd44ef0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb60e0b77a2d18fc1801e47347f59ad8973d3bcfc04b2eea02a86c571ddc9c79f7113f991887871b609dc42662d801133d2bfb00d3fe4a05e2137cefbe11473b110000000b00729de77808bd11a97d54a8be4e5d9af1f2e8c5a951161581a264260e47c1ac9f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb625c3df396d1793a71633fb37f33c32ef838940f179b598054aeb99eaf477e5ed2bb1f91f13f98830d42f2ce6199fdd4e6f7c522cab6755fbfbf523ee3f23f78c0000021c000000b01ee2460e1c13a3418fc211447e74832acefad1475bd85932de4c6a6e0f2c6145f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb60d17f85d276dc5a455d07c3a0b49840e0b219d95c6c31f4ecb2fcc642f282c6813061242ce4fba2e13cbade831ad2e6cf714aed0f874dd457c39566779d43e07000000b006365f31d669d53ecf5e92469681d44956932deba8e5e07c5e909ce749dca7c0fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb624d05ff3c2f597cb4dbd42f2a4d82d89baede2828d8a17298f55f47159d872e42abe79d969d78c550bb874a0cb3bd7e8a6e0f3bdbf3bd520405f7e74a4848483000000b01e5650de41fa32597666818e504838469cbef6822cb478a252f3262ae9b5fb6d00440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb60c2479177d4bc9c88d59c3f4bce57ea842863f26da979e730f9a26ea9488b95f121292fd242dbe524b54f5a2e34929072e7950620c495c69c0a3b0eddf34cafe0000021c000000b005aa6a01fc506456b6030290685589652457532679c1ffebd33758a4246641e804440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb624446ac3e8dc26e33461b33c76abe2a588b207bd5e66369903fcb02e34620d0c2a3284a98fbe1b6cf25ce4ea9d0f8d0474a518f89017f48fb5063a317f0e1eab000000b01d62d19897d8367dadefc94901e432e0d42398134088f7c6975d80b14f16886408440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb60b9883e7a33258e073fe343e8eb933c4104a6461ab73bde28440e2a76f12538711869dcd4a144d6a31f965ecb51cde22fc3d759cdd257bd9354a6caab9be6526000000b004b6eabc522e687aed8c4a4b19f183ff5bbbf4b78d967f1017a1b32a89c6cedf0c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb62350eb7e3eba2b076beafaf72847dd3fc016a94e723ab5bd48670ab499c29a03293f0563e59c1f9129e62ca54eab879eac09ba89a3ec73b3f97094b7e46eaba200000fa400000168000000b01c6f5252edb63aa1e5791103b3802d7b0b8839a4545d76eadbc7db37b477155b10440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb60aa504a1f9105d04ab877bf940552e5e47af05f2bf483d06c8ab3d2dd472e07e10931e879ff2518e6982ada766b8d8bd33a2172df0f9fafd79b4c7311f1ef21d000000b003c36b76a80c6c9f25159205cb8d7e9993209648a16afe345c0c0db0ef275bd614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb6225d6c3894982f2ba37442b1d9e3d7d9f77b4adf860f34e18cd1653aff2326fa284b861e3b7a23b5616f746000478238e36e5c1ab7c0f2d83ddaef3e49cf389900000168000000b01b7bd30d43943ec61d0258be651c281542ecdb356831f60f203235be19d7a25218440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb609b1855c4eee6128e310c3b3f1f128f87f13a783d31cbc2b0d1597b439d36d750f9f9f41f5d055b2a10bf5621854d3576b06b8bf04ce7a21be1f21b7847f7f14000000b002cfec30fdea70c35c9ed9c07d297933ca8537d9b53f7d58a07668375487e8cd1c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb62169ecf2ea76334fdafd8a6c8b7fd2742edfec7099e3b405d13bbfc16483b3f1275806d8915827d998f8bc1ab1e37cd31ad2fdabcb9571fc824549c4af2fc59000000168000000b01a8853c7997242ea548ba07916b822af7a517cc67c067533649c90447f382f4920440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb608be0616a4cc654d1a9a0b6ea38d2392b6784914e6f13b4f517ff23a9f33fa6c0eac1ffc4bae59d6d8953d1cc9f0cdf1a26b5a5018a2f94602897c3de9e00c0b000000b001dc6ceb53c874e79428217b2ec573ce01e9d96ac913fc7ce4e0c2bdb9e875c424440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb620766dad405437741286d2273d1bcd0e66448e01adb8332a15a61a47c9e440e826648792e7362bfdd08203d5637f776d52379f3cdf69f120c6afa44b1490528700000168000000b01994d481ef50470e8c14e833c8541d49b1b61e578fdaf457a906eacae498bc4028440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb607ca86d0faaa69715223532955291e2ceddceaa5fac5ba7395ea4cc1049487630db8a0b6a18c5dfb101e84d77b8cc88bd9cffbe12c77786a46f3d6c44f409902000000b000e8eda5a9a6790bcbb16935e0616e68394e7afbdce87ba1294b1d441f4902bb2c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb61f82ee6796323b984a1019e1eeb7c7a89da92f92c18cb24e5a1074ce2f44cddf2571084d3d143022080b4b90151b7207899c40cdf33e70450b19fed179f0df7e00000168000000b018a1553c452e4b32c39e2fee79f017e3e91abfe8a3af737bed71455149f9493730440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb606d7078b50886d9589ac9ae406c518c725418c370e9a3997da54a74769f5145a0cc52170f76a621f47a7cc922d28c32611349d72404bf78e8b5e314ab4a125f9000000b03059bcd2e0b61d59bb8af6a7137ec15f98e704d56a766b56b1976d5e74a98fb334440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb61e8f6f21ec103fbc8199619ca053c242d50dd123d56131729e7acf5494a55ad6247d890792f234463f94934ac6b76ca1c100e25f0712ef694f845957df516c7500000168000000b017add5f69b0c4f56fb2777a92b8c127e207f6179b783f2a031db9fd7af59d62e38440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb605e38845a66671b9c135e29eb86113615ca62dc8226eb8bc1ebf01cdcf55a1510bd1a22b4d4866437f31144cdec4bdc048993f03542076b2cfc88bd11a01b2f0000000b02f663d8d3694217df3143e61c51abbf9d04ba6667e4aea7af601c7e4da0a1caa3c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb61d9befdc41ee43e0b922a95751efbcdd0c7272b4e935b096e2e529dafa05e7cd238a09c1e8d0386a771ddb057853673bf86583f01ae76e8d93eeb3de44b1f96c00000168000000b016ba56b0f0ea537b32b0bf63dd280d1857e4030acb5871c47645fa5e14ba632540440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb604f008fffc4475ddf8bf2a5969fd0dfb940acf59364337e063295c5434b62e480ade22e5a3266a67b6ba5c079060b85a7ffde09467f4f5d71432e6577f623fe7000000b02e72be478c7225a22a9d861c76b6b69407b047f7921f699f3a6c226b3f6aa9a144440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb61ca8709697cc4804f0abf112038bb77743d71445fd0a2fbb274f84615f6674c422968a7c3eae3c8eaea722c029ef61d62fca25812ebbedb1d8590e64aa12866300000168000000b015c6d76b46c8579f6a3a071e8ec407b28f48a49bdf2cf0e8bab054e47a1af01c48440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb603fc89ba52227a02304872141b990895cb6f70ea4a17b704a793b6da9a16bb3f09eaa39ff9046e8bee43a3c241fcb2f4b76282257bc974fb589d40dde4c2ccde000000b02d7f3f01e25029c66226cdd72852b12e3f14e988a5f3e8c37ed67cf1a4cb36984c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb61bb4f150edaa4c29283538ccb527b2117b3bb5d710deaedf6bb9dee7c4c701bb21a30b36948c40b2e6306a7adb8b5c70672ec71242906cd61cc368eb0f73135a00000168000000b014d358259ca65bc3a1c34ed94060024cc6ad462cf301700cff1aaf6adf7b7d1350440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb603090a74a8007e2667d1b9cecd35033002d4127b5dec3628ebfe1160ff77483608f7245a4ee272b025cceb7cf398ad8eeec723b68f9df41f9d079b644a2359d5000000b02c8bbfbc382e2dea99b01591d9eeabc876798b19b9c867e7c340d7780a2bc38f54440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb61ac1720b4388504d5fbe808766c3acabb2a0576824b32e03b024396e2a278eb220af8bf0ea6a44d71db9b2358d27570a9e9368a35664ebfa612dc37174d3a05100000168000000b013dfd8dff2845fe7d94c9693f1fbfce6fe11e7be06d5ef31438509f144dc0a0a58440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb602158b2efdde824a9f5b01897ed0fdca3a38b40c71c0b54d30686be764d7d52d0803a514a4c076d45d563337a534a829262bc547a3727343e171f5eaaf83e6cc000000b02b9840768e0c320ed1395d4c8b8aa662adde2caacd9ce70c07ab31fe6f8c50865c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb619cdf2c5996654719747c842185fa745ea04f8f93887ad27f48e93f48f881ba91fbc0cab404848fb5542f9f03ec351a4d5f80a346a396b1ea5981df7da342d4800000168000000b012ec599a4862640c10d5de4ea397f7813576894f1aaa6e5587ef6477aa3c970160440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb601220be953bc866ed6e44944306cf864719d559d8595347174d2c66dca386224071025cefa9e7af894df7af256d0a2c35d9066d8b746f26825dc507114e473c3000000b02aa4c130e3ea363308c2a5073d26a0fce542ce3be17166304c158c84d4ecdd7d64440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb618da737fef445895ced10ffcc9fba1e021699a8a4c5c2c4c38f8ee7af4e8a8a01ec88d6596264d1f8ccc41aaf05f4c3f0d5cabc57e0dea42ea02787e3f94ba3f", + "archive": "0x2dc99418d20f84e8371cb2da22d700194b0d60d5575dbd584b804a2b4fcfe0eb", + "body": "0x00000010050c8e952ffe92c64a212618be5c82c44184fc01180adec9c293825c7f58f834119b62f02f05fa779848f6a83d8eb92256b845c4e8462920d5feadf959eb7af4096b1f3e104185fb470075a89bab39ad27bdd9639790b809efcbb855c98619c626173a65d27afc816dd87e16da93dd6c20398e6381492ccee2d3b8e5a5286eb61a51663b698760ee967a73ce2b0e8a573eea8d27b1aad5d26ee3540dd13890f0220b31cc9e75f68a065223549a26c96b959854c784ec94b1a377d01bdd9109af0100428a075c7e181ce1d02786f74ff2d3dcc81904334fb88de1410dbb3db6fc17d7d7ac1e060db2bc6c5089b3247b6dba15c6734af0fc35a8b5460db1a521371c3f34bba22d272493b677aa950bf6f3833f2c190286f4282a515112cf9a41430ad4a6e63620c3cf803c63c4854a66efa7e1f035fba42b62d3e1cfc2762073c3248d5de77dcbb665f8f7748a3cfb8bc6732166ac1894e1e5a47f0943d701dd2724e5cb8c11aff8a44b8a0916cc3ee6e5e66d648c25d980324fb7ad63c49fd2831a137b12914a05ee908312a5157f9509123033bc34e814a8501d5cc22e8ed70a2c27c1eef3573d466254ac9777466c61dd447e91a2ade6ef6e65216b9fd6e1ae0f0b7047b4d65c3f4eeb56b140bf0a7388b0bd1f1f1240ded1634b9de57309c11355eaafafc17f599b7108bf8501d83f115226eae0698a16af68f692b187333e00000004380000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024100000000000000000000000000000000000000000000000000000000000002420000000000000000000000000000000000000000000000000000000000000243000000000000000000000000000000000000000000000000000000000000024400000000000000000000000000000000000000000000000000000000000002450000000000000000000000000000000000000000000000000000000000000246000000000000000000000000000000000000000000000000000000000000024700000000000000000000000000000000000000000000000000000000000002480000000000000000000000000000000000000000000000000000000000000249000000000000000000000000000000000000000000000000000000000000024a000000000000000000000000000000000000000000000000000000000000024b000000000000000000000000000000000000000000000000000000000000024c000000000000000000000000000000000000000000000000000000000000024d000000000000000000000000000000000000000000000000000000000000024e000000000000000000000000000000000000000000000000000000000000024f0000000000000000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000000000000000000025100000000000000000000000000000000000000000000000000000000000002520000000000000000000000000000000000000000000000000000000000000253000000000000000000000000000000000000000000000000000000000000025400000000000000000000000000000000000000000000000000000000000002550000000000000000000000000000000000000000000000000000000000000256000000000000000000000000000000000000000000000000000000000000025700000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000259000000000000000000000000000000000000000000000000000000000000025a000000000000000000000000000000000000000000000000000000000000025b000000000000000000000000000000000000000000000000000000000000025c000000000000000000000000000000000000000000000000000000000000025d000000000000000000000000000000000000000000000000000000000000025e000000000000000000000000000000000000000000000000000000000000025f0000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026100000000000000000000000000000000000000000000000000000000000002620000000000000000000000000000000000000000000000000000000000000263000000000000000000000000000000000000000000000000000000000000026400000000000000000000000000000000000000000000000000000000000002650000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000026700000000000000000000000000000000000000000000000000000000000002680000000000000000000000000000000000000000000000000000000000000269000000000000000000000000000000000000000000000000000000000000026a000000000000000000000000000000000000000000000000000000000000026b000000000000000000000000000000000000000000000000000000000000026c000000000000000000000000000000000000000000000000000000000000026d000000000000000000000000000000000000000000000000000000000000026e000000000000000000000000000000000000000000000000000000000000026f00000000000000000000000000000000000000000000000000000000000002700000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000027200000000000000000000000000000000000000000000000000000000000002730000000000000000000000000000000000000000000000000000000000000274000000000000000000000000000000000000000000000000000000000000027500000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000277370000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000003420000000000000000000000000000000000000000000000000000000000000343000000000000000000000000000000000000000000000000000000000000034400000000000000000000000000000000000000000000000000000000000003450000000000000000000000000000000000000000000000000000000000000346000000000000000000000000000000000000000000000000000000000000034700000000000000000000000000000000000000000000000000000000000003480000000000000000000000000000000000000000000000000000000000000349000000000000000000000000000000000000000000000000000000000000034a000000000000000000000000000000000000000000000000000000000000034b000000000000000000000000000000000000000000000000000000000000034c000000000000000000000000000000000000000000000000000000000000034d000000000000000000000000000000000000000000000000000000000000034e000000000000000000000000000000000000000000000000000000000000034f0000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000035100000000000000000000000000000000000000000000000000000000000003520000000000000000000000000000000000000000000000000000000000000353000000000000000000000000000000000000000000000000000000000000035400000000000000000000000000000000000000000000000000000000000003550000000000000000000000000000000000000000000000000000000000000356000000000000000000000000000000000000000000000000000000000000035700000000000000000000000000000000000000000000000000000000000003580000000000000000000000000000000000000000000000000000000000000359000000000000000000000000000000000000000000000000000000000000035a000000000000000000000000000000000000000000000000000000000000035b000000000000000000000000000000000000000000000000000000000000035c000000000000000000000000000000000000000000000000000000000000035d000000000000000000000000000000000000000000000000000000000000035e000000000000000000000000000000000000000000000000000000000000035f0000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036100000000000000000000000000000000000000000000000000000000000003620000000000000000000000000000000000000000000000000000000000000363000000000000000000000000000000000000000000000000000000000000036400000000000000000000000000000000000000000000000000000000000003650000000000000000000000000000000000000000000000000000000000000366000000000000000000000000000000000000000000000000000000000000036700000000000000000000000000000000000000000000000000000000000003680000000000000000000000000000000000000000000000000000000000000369000000000000000000000000000000000000000000000000000000000000036a000000000000000000000000000000000000000000000000000000000000036b000000000000000000000000000000000000000000000000000000000000036c000000000000000000000000000000000000000000000000000000000000036d000000000000000000000000000000000000000000000000000000000000036e000000000000000000000000000000000000000000000000000000000000036f00000000000000000000000000000000000000000000000000000000000003700000000000000000000000000000000000000000000000000000000000000371000000000000000000000000000000000000000000000000000000000000037200000000000000000000000000000000000000000000000000000000000003730000000000000000000000000000000000000000000000000000000000000374000000000000000000000000000000000000000000000000000000000000037500000000000000000000000000000000000000000000000000000000000003760200000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000441100000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064a0000000000000000000000000000000000000000000000000000000000000641000000000000000000000000000000000000000000000000000000000000064b0000000000000000000000000000000000000000000000000000000000000642000000000000000000000000000000000000000000000000000000000000064c0000000000000000000000000000000000000000000000000000000000000643000000000000000000000000000000000000000000000000000000000000064d0000000000000000000000000000000000000000000000000000000000000644000000000000000000000000000000000000000000000000000000000000064e0000000000000000000000000000000000000000000000000000000000000645000000000000000000000000000000000000000000000000000000000000064f00000000000000000000000000000000000000000000000000000000000006460000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064700000000000000000000000000000000000000000000000000000000000006510000000000000000000000000000000000000000000000000000000000000648000000000000000000000000000000000000000000000000000000000000065200000000000000000000000000000000000000000000000000000000000006490000000000000000000000000000000000000000000000000000000000000653000000000000000000000000000000000000000000000000000000000000064a0000000000000000000000000000000000000000000000000000000000000654000000000000000000000000000000000000000000000000000000000000064b0000000000000000000000000000000000000000000000000000000000000655000000000000000000000000000000000000000000000000000000000000064c0000000000000000000000000000000000000000000000000000000000000656000000000000000000000000000000000000000000000000000000000000064d0000000000000000000000000000000000000000000000000000000000000657000000000000000000000000000000000000000000000000000000000000064e0000000000000000000000000000000000000000000000000000000000000658000000000000000000000000000000000000000000000000000000000000064f0000000000000000000000000000000000000000000000000000000000000659000011000000021c000000b0077b5c368ffa09fdd51759d71d7c0270712abf2ee3977a6d1da827e6616f539f88410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb688410eb626155cf87c85cc8a53760a832bd25bb0d58573c5c83bb11a4e6d7f70716b1ec32c0376de2367c11411713c315236060fc1788500f9ed6f10ff770973bc173062000000b01f33c3cd2b81dc24cd04208fb70aabec20f7041baa5e7247e1ce4ff38c1f9a1b8c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb68c410eb60d69761c36dbfe8793128b8543dfaccf5d1dd06a15493863ceb1b1e9ac1b653e13579001ddbdf311510dbd336a43572e4910e1a546faf65a7fbb3becf6c776dd000000b00687dcf0e5d80e220ca0a191cf17fd0aa88f60bff76bf9916212826cc6cfe09690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb690410eb62521ddb2d263d0ae8aff523ddd6e564b0cea1556dc10303e92d7d9f6d6cbabba2b0ff7987945c53848fa83ec03d200a9f8dd26920dc1ee3543e163fa2177bd590000021c000000b01e404487815fe049048d684a68a6a686585ba5acbe32f16c2638aa79f180271294410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb694410eb60c75f6d68cba02abca9bd33ff57ba769948271fb291db788131c0c70117bf235126410bc339bf735889704ee1bdf51c8807583365acf757ec42596735c2803d4000000b005945dab3bb612464429e94c80b3f7a4dff402510b4078b5a67cdcf32c306d8d98410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb698410eb6242e5e6d2841d4d2c28899f88f0a50e5444eb6e7efe4af62d742347d3c2c38b12a1c7852cf23c95c8083cba6b56dfb443041c82321966d59884bbe8086d84a50000000b01d4cc541d73de46d3c16b0051a42a1208fc0473dd20770906aa3050056e0b4099c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb69c410eb60b827790e29806d002251afaa717a203cbe7138c3cf236ac578666f676dc7f2c117091768979fb59c0204ca8cd7b4c62b7da24c76ea3f4a3088ff0f9c18890cb0000021c000000b004a0de659194166a7bb33107324ff23f1758a3e21f14f7d9eae737799190fa84a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6a0410eb6233adf277e1fd8f6fa11e1b340a64b7f7bb3587903b92e871bac8f03a18cc5a82928f90d2501cd80b80d13616709f5de67a669b4356aec7dccb61906ec38d747000000b01c5945fc2d1be891739ff7bfcbde9bbac724e8cee5dbefb4af0d5f86bc414100a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb6a4410eb60a8ef84b38760af439ae62b558b39c9e034bb51d50c6b5d09bf0c17cdc3d0c23107d1230df57ff7df7a994637f1746fcef3ec658827873c74cfa4b8026e91dc2000000b003ad5f1fe7721a8eb33c78c1e3ebecd94ebd457332e976fe2f5191fff6f1877ba8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb6a8410eb622475fe1d3fddd1b319b296df2424619b317fa0a178dadab6016e98a06ed529f283579c77adfd1a4ef965b1c18a5f0789f0b0b45493f6ba21120738d5199643e0000021c000000b01b65c6b682f9ecb5ab293f7a7d7a9654fe898a5ff9b06ed8f377ba0d21a1cdf7ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6ac410eb6099b79058e540f187137aa700a4f97383ab056ae649b34f4e05b1c03419d991a0f8992eb353603a22f32dc1e30b3419726a367e9964cf2eb9164a6068c49aab9000000b002b9dfda3d501eb2eac5c07c9587e7738621e70446bdf62273bbec865c521472b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb6b0410eb62153e09c29dbe13f69247128a3de40b3ea7c9b9b2b622ccfa48144106c4ddf962741fa81d0bdd5c9271fa2d6ca41eb12d66facd65d13eac6558ace13b6f9f135000000b01a724770d8d7f0d9e2b287352f1690ef35ee2bf10d84edfd37e2149387025aeeb4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb6b4410eb608a7f9bfe432133ca8c0f22abbeb91d27214f83f786fb41924c57689a6fe26110e9613a58b1407c666bc23d8e24f3c315e08097aaa21720fd5cf008cf1aa37b00000021c000000b001c66094932e22d7224f08374723e20dbd8688955a927546b826470cc1b2a169b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6b8410eb6206061567fb9e563a0adb8e3557a3b4e21e13d2c3f36abf3e8eb9e96d1ae6c8d264e7b3c269bd9ed5ea8ea917bdde5ad0dd44e6770e869ea99f5289a1c5a7e2c000000b0197ec82b2eb5f4fe1a3bceefe0b28b896d52cd8221596d217c4c6f19ec62e7e5bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb6bc410eb607b47a7a3a101760e04a39e56d878c6ca97999d08c44333d692fd1100c5eb3080da2945fe0f20bea9e456b9393eb36cb956cab0bbdf5f1341a395b13570ac4a7000000b000d2e14ee90c26fb59d84ff1f8bfdca7f4eb2a266e66f46afc90a19327132e60c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb6c0410eb61f6ce210d597e987d837009e071635e85945debd530b2b182d55f91d370ef984255afbf67c79de119632324c2d79e0474538eff884bce90ede5f832081bb0b230000021c000000b0188b48e58493f92251c516aa924e8623a4b76f13352dec45c0b6c9a051c374dcc4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb6c4410eb606c0fb348fee1b8517d381a01f238706e0de3b61a018b261ad9a2b9671bf3fff0caf151a36d0100ed5ceb34e45873165ccd14c9cd1ca70585ea3b599bc6b519e000000b03043b07c201bcb4949b1dd632bdd2f9f5483b3fffbf4e42084dcf1ad7c73bb58c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb6c8410eb61e7962cb2b75edac0fc04858b8b2308290aa804e66dfaa3c71c053a39c6f867b24677cb0d257e235cdbb7a06df15dae17c9d91899891683322c9dda6e71b981a000000b01797c99fda71fd46894e5e6543ea80bddc1c10a449026b6a05212426b72401d3cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb6cc410eb605cd7beee5cc1fa94f5cc95ad0bf81a11842dcf2b3ed3185f204861cd71fccf60bbb95d48cae14330d57fb08f7232c000435ee2de59eef7ca30e102021cbde950000021c000000b02f50313675f9cf6d813b251ddd792a398be855910fc96344c9474c33e1d4484fd0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb6d0410eb61d85e3858153f1d0474990136a4e2b1cc80f21df7ab42960b62aae2a01d013722373fd6b2835e65a0544c1c190b1d57bb402331aac65e7576734382d4c7c2511000000b016a44a5a3050016ac0d7a61ff5867b581380b2355cd6ea8e498b7ead1c848ecad4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb6d4410eb604d9fca93baa23cd86e61115825b7c3b4fa77e83c7c1b0aa366ee0a33c8059ed0ac8168ee28c185744e142c3a8bf269a3b9a8fbef9736ea0e7786aa6872c6b8c000000b02e5cb1f0cbd7d391b8c46cd88f1524d3c34cf722239de2690db1a6ba4734d546d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb6d8410eb61c92643fd731f5f47ed2d7ce1bea25b6ff73c3708e88a884fa9508b06730a06922807e257e13ea7e3cce097c424dd015eb66d4abc03a667bab9e92b3b1dcb2080000021c000000b015b0cb14862e058ef860eddaa72275f24ae553c670ab69b28df5d93381e51bc1dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb6dc410eb603e67d63918827f1be6f58d033f776d5870c2014db962fce7ad93b29a1e0e6e409d49749386a1c7b7c6a8a7e5a5b213472ff31500d47edc52be2c52cec8cf883000000b02d6932ab21b5d7b5f04db49340b11f6dfab198b33772618d521c0140ac95623de0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb6e0410eb61b9ee4fa2d0ffa18b65c1f88cd86205136d86501a25d27a93eff6336cc912d60218cfedfd3f1eea274575136f3e9cab022cb763cd40ee59ff008ed3a173d3eff000000b014bd4bcedc0c09b32fea359558be708c8249f557847fe8d6d26033b9e745a8b8e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb6e4410eb602f2fe1de7662c15f5f8a08ae593716fbe70c1a5ef6aaef2bf4395b0074173db08e118038e48209fb3f3d2390bf71bceaa63d2e1211c6ce9704d1fb351ed857a00000fa400000168000000b02c75b3657793dbda27d6fc4df24d1a0832163a444b46e0b196865bc711f5ef34e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb6e8410eb61aab65b482edfe3cede567437f221aeb6e3d0692b631a6cd8369bdbd31f1ba5720997f9a29cff2c6abe098f1a585c54a5a3017cde7e364c4347347c07c9dcbf6000000b013c9cc8931ea0dd767737d500a5a6b26b9ae96e8985467fb16ca8e404ca635afec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb6ec410eb601ff7ed83d44303a2d81e845972f6c09f5d56337033f2e1703adf0366ca200d207ed98bde42624c3eb7d19f3bd931668e1c8747234f0ec0db4b77a39b74e127100000168000000b02b82341fcd71dffe5f604408a3e914a2697adbd55f1b5fd5daf0b64d77567c2bf0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb6f0410eb619b7e66ed8cc0261256eaefe30be1585a5a1a823ca0625f1c7d418439752474e1fa600547fadf6eae369e0ac5721bfe49194b95efbb7e3e878dda246e1fe58ed000000b012d64d4387c811fb9efcc50abbf665c0f1133879ac28e71f5b34e8c6b206c2a6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6f4410eb6010bff929322345e650b300048cb66a42d3a04c81713ad3b48184abcd2028dc906fa19783a0428e8230661ae6f2f1103192d160348c56b31f921d4c01cae9f6800000168000000b02a8eb4da234fe42296e98bc355850f3ca0df7d6672efdefa1f5b10d3dcb70922f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb6f8410eb618c467292eaa06855cf7f6b8e25a101fdd0649b4dddaa5160c3e72c9fcb2d4451eb2810ed58bfb0f1af3286708bdba7ec8f95af00f8c630cbd47fccd475ee5e4000000b011e2cdfddda6161fd6860cc56d92605b2877da0abffd66439f9f434d17674f9dfc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb6fc410eb60018804ce90038829c9477bafa67613e649ea6592ae82c5f8c82a54337631ac006069a328fe22d0c5a8fa96920cb0b9d5091b7945c99ea563d8c2f46820f2c5f00000168000000b02a02bfaa4936733a7d8dfc0d2758c4586ea3a2a143cbfe699401cc90b740a34a00420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb600420eb617d0e7e384880aa994813e7393f60aba146aeb45f1af243a50a8cd506213613c1dbf01c92b69ff33527c7021ba59b519005dfc812360e23101b25753acbf72db000000b01156d8ce038ca537bd2a7d0f3f661576f63bff4590d985b31445ff09f1f0e9c504420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb604420eb62ff0d98ff01867c43b892dbb4dbc6eb75a96b3dc757dbc60450b569401ecb4e9057aa502b5c8bc24413419b2f29ec0b91e55dccf2d7609c5b232eb035c98c68700000168000000b0290f40649f14775eb51743c7d8f4bef2a608443257a07d8dd86c27171ca1304108420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb608420eb61744f2b3aa6e99c17b25aebd65c9bfd5e22f1080c28b43a9c54f890d3c9cfb641d330c9951508e4b3920e06b8c2d6a34ce2221bbf43d01a07659131087490d03000000b010635988596aa95bf4b3c4c9f10210112da0a0d6a4ae04d758b05990575176bc0c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb60c420eb62efd5a4a45f66be873127575ff58695191fb556d89523b848975b11a674d41e0048725bd0ba6c04878bd616da43abb5355ba7e60414a88e9f69d4589c1f9537e00000168000000b0281bc11ef4f27b82eca08b828a90b98cdd6ce5c36b74fcb21cd6819d8201bd3810420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb610420eb61651736e004c9de5b2aef6781765ba701993b211d65fc2ce09b9e393a1fd885b1c3f8d53a72e926f70aa28263dc964cf0586c34d081180c4bac36d96eca999fa000000b00f6fda42af48ad802c3d0c84a29e0aab65054267b88283fb9d1ab416bcb203b314420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb614420eb62e09db049bd4700caa9bbd30b0f463ebc95ff6fe9d26baa8cde00ba0ccadced70393a6776184c46cb046a92855d6b5ed8d1f1ff1551f080e3b07a0102759e07500000168000000b0272841d94ad07fa72429d33d3c2cb42714d187547f497bd66140dc23e7624a2f18420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb618420eb6155df428562aa209ea383e32c901b50a50f853a2ea3441f24e243e1a075e15521b4c0e0dfd0c9693a8336fe0ef655f693ceb64de1be5ffe8ff2dc81d520a26f1000000b00e7c5afd0526b1a463c6543f543a05459c69e3f8cc57031fe1850e9d221290aa1c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb61c420eb62d165bbef1b27430e22504eb62905e8600c4988fb0fb39cd124a6627320e5bce02a02731b762c890e7cff0e30772b087c483c18268f387327f71fa968cba6d6c00000168000000b02634c293a0ae83cb5bb31af7edc8aec14c3628e5931dfafaa5ab36aa4cc2d72620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb620420eb6146a74e2ac08a62e21c185ed7a9dafa4885cf533fe08c116928e98a06cbea2491a588ec852ea9ab7dfbcb79ba1015a037450066f2fba7f0d439822a3b76ab3e8000000b00d88dbb75b04b5c89b4f9bfa05d5ffdfd3ce8589e02b824425ef692387731da124420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb624420eb62c22dc794790785519ae4ca6142c592038293a20c4cfb8f156b4c0ad976ee8c501aca7ec0d40ccb51f59389db90eab21fbe863137cc80656c3dc551cf21afa6300000168000000b02541434df68c87ef933c62b29f64a95b839aca76a6f27a1eea159130b223641d28420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb628420eb61376f59d01e6aa52594acda82c39aa3ebfc196c511dd403ad6f8f326d21f2f4019650f82a8c89edc1745ff56529d549dabb4a800438efe3188027d2a1ccb40df000000b00c955c71b0e2b9ecd2d8e3b4b771fa7a0b33271af40001686a59c3a9ecd3aa982c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62c420eb62b2f5d339d6e7c7951379460c5c853ba6f8ddbb1d8a438159b1f1b33fccf75bc00b928a6631ed0d956e280586aaaa5bc334d04a4909c857b0846afa3577b875a00000168000000b0244dc4084c6a8c13cac5aa6d5100a3f5baff6c07bac6f9432e7febb71783f11430420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb630420eb61283765757c4ae7690d41562ddd5a4d8f726385625b1bf5f1b634dad377fbc371871903cfea6a3004ecf471104394f37e319499157637d55cc6cd7b0822bcdd6000000b00ba1dd2c06c0be110a622b6f690df5144297c8ac07d4808caec41e305234378f34420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb634420eb62a3bddedf34c809d88c0dc1b77644e54a6f27d42ec78b739df8975ba623002b33029f7d39a2e752746bc0dc99dc7f8b392e58e7e1e2a75309092ffbdacdc145200000168000000b0235a44c2a2489038024ef228029c9e8ff2640d98ce9b786772ea463d7ce47e0b38420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb638420eb6118ff711ada2b29ac85d5d1d8f719f732e8ad9e739863e835fcda8339ce0492e177e10f75484a72486588ecbb5d549d21a7deb226b37fc7a10d73236e78c5acd000000b00aae5de65c9ec23541eb732a1aa9efae79fc6a3d1ba8ffb0f32e78b6b794c4863c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb63c420eb629485ea8492a84c1c04a23d6290048eede571ed4004d365e23f3d040c7908faa2f36788df00c794b7e4555844f63f34dca4a300f31fef454d4fd5a44123ca149380000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000028100000000000000000000000000000000000000000000000000000000000002820000000000000000000000000000000000000000000000000000000000000283000000000000000000000000000000000000000000000000000000000000028400000000000000000000000000000000000000000000000000000000000002850000000000000000000000000000000000000000000000000000000000000286000000000000000000000000000000000000000000000000000000000000028700000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000289000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000028b000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028d000000000000000000000000000000000000000000000000000000000000028e000000000000000000000000000000000000000000000000000000000000028f0000000000000000000000000000000000000000000000000000000000000290000000000000000000000000000000000000000000000000000000000000029100000000000000000000000000000000000000000000000000000000000002920000000000000000000000000000000000000000000000000000000000000293000000000000000000000000000000000000000000000000000000000000029400000000000000000000000000000000000000000000000000000000000002950000000000000000000000000000000000000000000000000000000000000296000000000000000000000000000000000000000000000000000000000000029700000000000000000000000000000000000000000000000000000000000002980000000000000000000000000000000000000000000000000000000000000299000000000000000000000000000000000000000000000000000000000000029a000000000000000000000000000000000000000000000000000000000000029b000000000000000000000000000000000000000000000000000000000000029c000000000000000000000000000000000000000000000000000000000000029d000000000000000000000000000000000000000000000000000000000000029e000000000000000000000000000000000000000000000000000000000000029f00000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a100000000000000000000000000000000000000000000000000000000000002a200000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000002a400000000000000000000000000000000000000000000000000000000000002a500000000000000000000000000000000000000000000000000000000000002a600000000000000000000000000000000000000000000000000000000000002a700000000000000000000000000000000000000000000000000000000000002a800000000000000000000000000000000000000000000000000000000000002a900000000000000000000000000000000000000000000000000000000000002aa00000000000000000000000000000000000000000000000000000000000002ab00000000000000000000000000000000000000000000000000000000000002ac00000000000000000000000000000000000000000000000000000000000002ad00000000000000000000000000000000000000000000000000000000000002ae00000000000000000000000000000000000000000000000000000000000002af00000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002b100000000000000000000000000000000000000000000000000000000000002b200000000000000000000000000000000000000000000000000000000000002b300000000000000000000000000000000000000000000000000000000000002b400000000000000000000000000000000000000000000000000000000000002b500000000000000000000000000000000000000000000000000000000000002b600000000000000000000000000000000000000000000000000000000000002b7370000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000038100000000000000000000000000000000000000000000000000000000000003820000000000000000000000000000000000000000000000000000000000000383000000000000000000000000000000000000000000000000000000000000038400000000000000000000000000000000000000000000000000000000000003850000000000000000000000000000000000000000000000000000000000000386000000000000000000000000000000000000000000000000000000000000038700000000000000000000000000000000000000000000000000000000000003880000000000000000000000000000000000000000000000000000000000000389000000000000000000000000000000000000000000000000000000000000038a000000000000000000000000000000000000000000000000000000000000038b000000000000000000000000000000000000000000000000000000000000038c000000000000000000000000000000000000000000000000000000000000038d000000000000000000000000000000000000000000000000000000000000038e000000000000000000000000000000000000000000000000000000000000038f0000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000039100000000000000000000000000000000000000000000000000000000000003920000000000000000000000000000000000000000000000000000000000000393000000000000000000000000000000000000000000000000000000000000039400000000000000000000000000000000000000000000000000000000000003950000000000000000000000000000000000000000000000000000000000000396000000000000000000000000000000000000000000000000000000000000039700000000000000000000000000000000000000000000000000000000000003980000000000000000000000000000000000000000000000000000000000000399000000000000000000000000000000000000000000000000000000000000039a000000000000000000000000000000000000000000000000000000000000039b000000000000000000000000000000000000000000000000000000000000039c000000000000000000000000000000000000000000000000000000000000039d000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000039f00000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a100000000000000000000000000000000000000000000000000000000000003a200000000000000000000000000000000000000000000000000000000000003a300000000000000000000000000000000000000000000000000000000000003a400000000000000000000000000000000000000000000000000000000000003a500000000000000000000000000000000000000000000000000000000000003a600000000000000000000000000000000000000000000000000000000000003a700000000000000000000000000000000000000000000000000000000000003a800000000000000000000000000000000000000000000000000000000000003a900000000000000000000000000000000000000000000000000000000000003aa00000000000000000000000000000000000000000000000000000000000003ab00000000000000000000000000000000000000000000000000000000000003ac00000000000000000000000000000000000000000000000000000000000003ad00000000000000000000000000000000000000000000000000000000000003ae00000000000000000000000000000000000000000000000000000000000003af00000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b100000000000000000000000000000000000000000000000000000000000003b200000000000000000000000000000000000000000000000000000000000003b300000000000000000000000000000000000000000000000000000000000003b400000000000000000000000000000000000000000000000000000000000003b500000000000000000000000000000000000000000000000000000000000003b60200000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000481100000000000000000000000000000000000000000000000000000000000000680000000000000000000000000000000000000000000000000000000000000068a0000000000000000000000000000000000000000000000000000000000000681000000000000000000000000000000000000000000000000000000000000068b0000000000000000000000000000000000000000000000000000000000000682000000000000000000000000000000000000000000000000000000000000068c0000000000000000000000000000000000000000000000000000000000000683000000000000000000000000000000000000000000000000000000000000068d0000000000000000000000000000000000000000000000000000000000000684000000000000000000000000000000000000000000000000000000000000068e0000000000000000000000000000000000000000000000000000000000000685000000000000000000000000000000000000000000000000000000000000068f00000000000000000000000000000000000000000000000000000000000006860000000000000000000000000000000000000000000000000000000000000690000000000000000000000000000000000000000000000000000000000000068700000000000000000000000000000000000000000000000000000000000006910000000000000000000000000000000000000000000000000000000000000688000000000000000000000000000000000000000000000000000000000000069200000000000000000000000000000000000000000000000000000000000006890000000000000000000000000000000000000000000000000000000000000693000000000000000000000000000000000000000000000000000000000000068a0000000000000000000000000000000000000000000000000000000000000694000000000000000000000000000000000000000000000000000000000000068b0000000000000000000000000000000000000000000000000000000000000695000000000000000000000000000000000000000000000000000000000000068c0000000000000000000000000000000000000000000000000000000000000696000000000000000000000000000000000000000000000000000000000000068d0000000000000000000000000000000000000000000000000000000000000697000000000000000000000000000000000000000000000000000000000000068e0000000000000000000000000000000000000000000000000000000000000698000000000000000000000000000000000000000000000000000000000000068f0000000000000000000000000000000000000000000000000000000000000699000011000000021c000000b02266c57cf826945c39d839e2b438992a29c8af29e26ff78bb754a0c3e2450b0240420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb640420eb6109c77cc0380b6beffe6a4d8410d9a0d65ef7b784d5abda7a43802ba0240d625168a91b1aa62ab48bde1d6866771446c51e28cb37f0c7b9e55418cbd4cece7c4000000b009badea0b27cc6597974bae4cc45ea48b1610bce2f7d7ed53798d33d1cf5517d44420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb644420eb62854df629f0888e5f7d36b90da9c438915bbc0651421b582685e2ac72cf11ca12e42f94845ea7d6fb5ce9d3f00ffede801aed1a045d373791967b4ca779d2e40000000b0217346374e0498807161819d65d493c4612d50baf64476affbbefb4a47a597f948420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb648420eb60fa8f886595ebae3376fec92f2a994a79d541d09612f3ccbe8a25d4067a1631c1597126c0040af6cf56b1e41190d3f0689472e4492e0fac299abe743b24d74bb0000021c000000b008c75f5b085aca7db0fe029f7de1e4e2e8c5ad5f4351fdf97c032dc38255de744c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb64c420eb62761601cf4e68d0a2f5cb34b8c383e234d2061f627f634a6acc8854d9251a9982d4f7a029bc88193ed57e4f9b29be8823913733159a7f29d5dd20f50dcfdbb37000000b0207fc6f1a3e29ca4a8eac95817708e5e9891f24c0a18f5d4402955d0ad0624f050420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb650420eb60eb57940af3cbf076ef9344da4458f41d4b8be9a7503bbf02d0cb7c6cd01f01314a39326561eb3912cf465fbcaa939a0c0abcfd5a6b579e6de1641ca17ae01b2000000b007d3e0155e38cea1e8874a5a2f7ddf7d202a4ef057267d1dc06d8849e7b66b6b54420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb654420eb6266de0d74ac4912e66e5fb063dd438bd848503873bcab3caf132dfd3f7b2368f2c5bfabcf1a685b824e12cb46437e31c707814c26d7c71c1a23c69d7425e482e0000021c000000b01f8c47abf9c0a0c8e0741112c90c88f8cff693dd1ded74f88493b0571266b1e758420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb658420eb60dc1f9fb051ac32ba6827c0855e189dc0c1d602b88d83b147177124d32627d0a13b013e0abfcb7b5647dadb67c45343af8107166ba89f90b22809c507d0e8ea9000000b006e060cfb416d2c620109214e119da17578ef0816afafc4204d7e2d04d16f8625c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb65c420eb6257a6191a0a295529e6f42c0ef703357bbe9a5184f9f32ef359d3a5a5d12c3862b687b77478489dc5c6a746f15d3ddb6a7dcb6538150f0e5e6a6c45da7bed525000000b01e98c8664f9ea4ed17fd58cd7aa88393075b356e31c1f41cc8fe0add77c73ede60420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb660420eb60cce7ab55af8c74fde0bc3c3077d8476438201bc9cacba38b5e16cd397c30a0112bc949b01dabbd99c06f5712de12ed52f7512f7ce5e782f66eaf6d6e26f1ba00000021c000000b005ece18a09f4d6ea5799d9cf92b5d4b18ef392127ecf7b6649423d56b277855964420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb664420eb62486e24bf6809976d5f88a7ba10c2df1f34e46a96373b2137a0794e0c273507d2a74fc319d628e0093f3bc29c76fd850df4157e49525700a2b111ee40d1f621c000000b01da54920a57ca9114f86a0882c447e2d3ebfd6ff459673410d686563dd27cbd568420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb668420eb60bdafb6fb0d6cb7415950b7db9197f107ae6a34db081395cfa4bc759fd2396f811c9155557b8bffdd3903d2bdf7d296f66d9b488e232f753ab55515d47cfa897000000b004f962445fd2db0e8f23218a4451cf4bc65833a392a3fa8a8dac97dd17d812506c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb66c420eb6239363064c5e9d9b0d81d23652a8288c2ab2e83a77483137be71ef6727d3dd7429817cebf3409224cb7d03e4790bd2eb16a5f975a8f9ef2e6f7b796a727fef130000021c000000b01cb1c9dafb5aad35870fe842dde078c776247890596af26551d2bfea428858cc70420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb670420eb60ae77c2a06b4cf984d1e53386ab579aab24b44dec455b8813eb621e0628423ef10d5960fad96c4220b1984e6911924099e3e5619f6077677efbfabe3ad30358e000000b00405e2feb5b0df32c6ac6944f5edc9e5fdbcd534a67879aed216f2637d389f4774420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb674420eb6229fe3c0a23ca1bf450b19f104442326621789cb8b1cb05c02dc49ed8d346a6b288dfda6491e964903064b9f2aa7cd854e0a9b06bcce6e52b3e5d3f0d7e07c0a000000b01bbe4a955138b159be992ffd8f7c7361ad891a216d3f7189963d1a70a7e8e5c378420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb678420eb609f3fce45c92d3bc84a79af31c517444e9afe66fd82a37a583207c66c7e4b0e60fe216ca0374c84642a2cca142b51ea3d5a2f7ab09dbf59c342a066a1290c2850000021c000000b0031263b90b8ee356fe35b0ffa789c480352176c5ba4cf8d316814ce9e2992c3e7c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb67c420eb621ac647af81aa5e37c9461abb5e01dc0997c2b5c9ef12f804746a473f294f762279a7e609efc9a6d3a8f9359dc43c81f856f3c97d0a2ed76f8502e773d410901000000b01acacb4fa716b57df62277b841186dfbe4edbbb28113f0addaa774f70d4972ba80420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb680420eb609007d9eb270d7e0bc30e2adcded6edf21148800ebfeb6c9c78ad6ed2d453ddd0eee97845952cc6a7a2c145bf451193e0d07993c1db074c0789460f077f14f7c000000b0021ee473616ce77b35bef8ba5925bf1a6c861856ce2177f75aeba77047f9b93584420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb684420eb620b8e5354df8aa07b41da966677c185ad0e0ccedb2c5aea48bb0fefa57f5845926a6ff1af4da9e917218db148ddfc2b9bcd3de28e4776c9b3cba88fda2a195f80000021c000000b019d74c09fcf4b9a22dabbf72f2b468961c525d4394e86fd21f11cf7d72a9ffb188420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb688420eb6080cfe59084edc04f3ba2a687f89697958792991ffd335ee0bf5317392a5cad40dfb183eaf30d08eb1b55c16a5ed13d8446c3acd3184f3e4bcfebb76dd51dc73000000b0012b652db74aeb9f6d4840750ac1b9b4a3eab9e7e1f5f71b9f5601f6ad5a462c8c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb68c420eb61fc565efa3d6ae2beba6f121191812f508456e7ec69a2dc8d01b5980bd56115025b37fd54ab8a2b5a9a222cf3f7bbd53f4387fb9f84bebbf8124e384080222ef000000b018e3ccc452d2bdc66535072da450633053b6fed4a8bceef6637c2a03d80a8ca890420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb690420eb607197f135e2ce0292b437223312564138fddcb2313a7b512505f8bf9f80657cb0d0798f9050ed4b2e93ea3d157890e727bd0dc5e45597309016915fd42b2696a0000021c000000b00037e5e80d28efc3a4d1882fbc5db44edb4f5b78f5ca763fe3c05c7d12bad32394420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb694420eb61ed1e6a9f9b4b250233038dbcab40d8f3faa100fda6eaced1485b40722b69e4724c0008fa096a6d9e12b6a89f117b7ee2b9d214b0c206ae3c58f3e0a6d62afe6000000b017f04d7ea8b0c1ea9cbe4ee855ec5dca8b1ba065bc916e1aa7e6848a3d6b199f98420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb698420eb60625ffcdb40ae44d62ccb9dde2c15eadc7426cb4277c343694c9e6805d66e4c20c1419b35aecd8d720c7eb8c0925090cb3357def592df22d45d37083a812f661000000b02fa8b5154438941194ab15a0ef7b07463ae7e552835865f56c0cac97681b601b9c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb69c420eb61dde67644f92b6745ab980967c500829770eb1a0ee432c1158f00e8d88172b3e23cc8149f674aafe18b4b244a2b3b2886301c2dc1ff4ea0809f99890d2c33cdd00000fa400000168000000b016fcce38fe8ec60ed44796a307885864c28041f6d065ed3eec50df10a2cba696a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb6a0420eb60532808809e8e8719a560198945d5947fea70e453b50b35ad9344106c2c771b90b209a6db0cadcfb58513346bac103a6ea9a1f806d0271518a3dcb0a0d738358000000b02eb535cf9a169835cc345d5ba11701e0724c86e3972ce519b077071dcd7bed12a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb6a4420eb61ceae81ea570ba989242c8512dec02c3ae7353320217ab359d5a6913ed77b83522d902044c52af22503df9ff544fad229a66646d33c9692c4e63f3173823c9d400000168000000b016094ef3546cca330bd0de5db92452fef9e4e387e43a6c6330bb3997082c338da8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6a8420eb6043f01425fc6ec95d1df495345f953e2360bafd64f25327f1d9e9b8d2827feb00a2d1b2806a8e11f8fda7b016c5cfe4121fec11180d6f075cea8259072d4104f000000b02dc1b689eff49c5a03bda51652b2fc7aa9b12874ab01643df4e161a432dc7a09ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb6ac420eb61bf768d8fb4ebebcc9cc100bdf87fd5de5d7f4c315ec2a59e1c4c39a52d8452c21e582bea230b34687c741ba05eba7bcd1cb05fe479de85092ce4d9d9d8456cb00000168000000b01515cfadaa4ace57435a26186ac04d9931498518f80eeb877525941d6d8cc084b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6b0420eb6034b81fcb5a4f0ba0968910df7954e7c6d70516762f9b1a36208f6138d888ba709399be25c86e543c763c2bc1df8f8db596362a294ab6f9a13128016d8349d46000000b02cce374445d2a07e3b46ecd1044ef714e115ca05bed5e362394bbc2a983d0700b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb6b4420eb61b03e993512cc2e1015557c69123f7f81d3c965429c0a97e262f1e20b838d22320f20378f80eb76abf508974b787a257092fa78f5b726774d738a82402e4e3c200000168000000b0142250680028d27b7ae36dd31c5c483368ae26aa0be36aabb98feea3d2ed4d7bb8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6b8420eb6025802b70b82f4de40f1d8c8a9314916a4d4f2f876ce30c7a6735099f2e9189e08461c9cb264e967feed0a76cf94f37590c80433a87feebe577cda9d3d952a3d000000b02bdab7fe9bb0a4a272d0348bb5eaf1af187a6b96d2aa62867db616b0fd9d93f7bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb6bc420eb61a106a4da70ac70538de9f8142bff29254a137e53d9528a26a9978a71d995f1a1ffe84334decbb8ef6d9d12f69239cf1409449206f46e6991ba302aa684570b900000168000000b0132ed1225606d69fb26cb58dcdf842cda012c83b1fb7e9cffdfa492a384dda72c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6c0420eb6016483716160f902787b20835acd43b0dc3994898aa2afebeaddab205849a59507529d570842ed8c367652318130ee0fc82ca5c4bc546de29be73523a2f5b734000000b02ae738b8f18ea8c6aa597c466786ec494fdf0d27e67ee1aac220713762fe20eec4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6c4420eb6191ceb07fce8cb297067e73bf45bed2c8c05d9765169a7c6af03d32d82f9ec111f0b04eda3cabfb32e6318ea1abf978b77f8eab1831b65bd600d5d30cda5fdb000000168000000b0123b51dcabe4dac3e9f5fd487f943d67d77769cc338c68f44264a3b09dae6769c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb6c8420eb60071042bb73efd26b004683e0c693e4b139e361a9e772f102f4805a6bdaa328c065f1e115e20f1b06dff99ec32cce8a9ff914755d028ed06e0518faa0856442b000000b029f3b973476caceae1e2c4011922e6e38743aeb8fa5360cf068acbbdc85eade5cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb6cc420eb618296bc252c6cf4da7f12ef6a5f7e7c6c36a7b07653e26eaf36e2db3e85a79081e1785a7f9a8c3d765ec60a4cc5b9225af5d8c4296efe4e1a477b7b733068aa700000168000000b01147d29701c2dee8217f4503313038020edc0b5d4760e81886cefe37030ef460d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb6d0420eb62fe1d358ee4ea1749fddf5af3f8691427336bff42c051ec5b79455c1130abf84056b9ecbb3fef5d4a588e1a6e468e34436f5e8e6e3fd6c2b24bbea306db6d122000000b029003a2d9d4ab10f196c0bbbcabee17dbea8504a0e27dff34af526442dbf3adcd4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb6d4420eb61735ec7ca8a4d371df7a76b15793e260facf1c987912a60f37d8883a4dbb05ff1d2406624f86c7fb9d75a85f7df78cbfe6c22dd3aac46405e8e2123d9867179e00000168000000b01054535157a0e30c59088cbde2cc329c4640acee5b35673ccb3958bd686f8157d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb6d8420eb62eee5413442ca598d7673d69f1228bdcaa9b61853fd99de9fbfeb047786b4c7b04781f8609dcf9f8dd1229619604ddde6e5a8a77f7d1eb4f692644b6d3175e19000000b0280cbae7f328b53350f553767c5adc17f60cf1db21fc5f178f5f80ca931fc7d3dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb6dc420eb616426d36fe82d7961703be6c092fdcfb3233be298ce725337c42e2c0b31b92f61c30871ca564cc1fd4fef01a2f93875a1e26cf64be98e32a2d4c6cc3fdc7a49500000168000000b00f60d40bad7ee7309091d47894682d367da54e7f6f09e6610fa3b343cdd00e4ee0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb6e0420eb62dfad4cd9a0aa9bd0ef08524a2be8676e200031653ae1d0e40690acdddcbd9720384a0405fbafe1d149b711c47a0d878a5bf2c090ba66a73ad909f3d3877eb10000000b027193ba24906b957887e9b312df6d6b22d71936c35d0de3bd3c9db50f88054cae4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6e4420eb6154eedf15460dbba4e8d0626bacbd79569985fbaa0bba457c0ad3d47187c1fed1b3d07d6fb42d0440c8837d4e12f81f4558b70f5d26d624e71b6c74a6328318c00000168000000b00e6d54c6035ceb54c81b1c33460427d0b509f01082de6585540e0dca33309b45e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb6e8420eb62d075587efe8ade14679ccdf545a81111964a4a767829c3284d36554432c6669029120fab59902414c24b8d6f93cd312dd23cd9a1f7ae997f1faf9c39dd87807000000b02625bc5c9ee4bd7bc007e2ebdf92d14c64d634fd49a55d60183435d75de0e1c1ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6ec420eb6145b6eabaa3edfde86164de16c67d22fa0fd014bb490237c051797cd7ddcace41a4988915120d46844117f8f92cb7c8e8cf01286e641e172b62121d0c888be8300000168000000b00d79d580593aef78ffa463edf7a0226aec6e91a196b2e4a9987868509891283cf0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb6f0420eb62c13d64245c6b2057e03149a05f67bab50c946387b571b56c93dbfdaa88cf360019da1b50b77066583ae0091aad8cdad14886f2b334f68bc3665544a033904fe000000b025323d16f4c2c19ff7912aa6912ecbe69c3ad68e5d79dc845c9e905dc3416eb8f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb6f4420eb61367ef66001ce402bd9f959c1e03ccc9d861a2dcc864a2a04981f253e33d39db1956094ba6fed88c7b9ac74a44677728c454b417fa166096fa8b7c572de94b7a3800000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002c100000000000000000000000000000000000000000000000000000000000002c200000000000000000000000000000000000000000000000000000000000002c300000000000000000000000000000000000000000000000000000000000002c400000000000000000000000000000000000000000000000000000000000002c500000000000000000000000000000000000000000000000000000000000002c600000000000000000000000000000000000000000000000000000000000002c700000000000000000000000000000000000000000000000000000000000002c800000000000000000000000000000000000000000000000000000000000002c900000000000000000000000000000000000000000000000000000000000002ca00000000000000000000000000000000000000000000000000000000000002cb00000000000000000000000000000000000000000000000000000000000002cc00000000000000000000000000000000000000000000000000000000000002cd00000000000000000000000000000000000000000000000000000000000002ce00000000000000000000000000000000000000000000000000000000000002cf00000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002d100000000000000000000000000000000000000000000000000000000000002d200000000000000000000000000000000000000000000000000000000000002d300000000000000000000000000000000000000000000000000000000000002d400000000000000000000000000000000000000000000000000000000000002d500000000000000000000000000000000000000000000000000000000000002d600000000000000000000000000000000000000000000000000000000000002d700000000000000000000000000000000000000000000000000000000000002d800000000000000000000000000000000000000000000000000000000000002d900000000000000000000000000000000000000000000000000000000000002da00000000000000000000000000000000000000000000000000000000000002db00000000000000000000000000000000000000000000000000000000000002dc00000000000000000000000000000000000000000000000000000000000002dd00000000000000000000000000000000000000000000000000000000000002de00000000000000000000000000000000000000000000000000000000000002df00000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002e100000000000000000000000000000000000000000000000000000000000002e200000000000000000000000000000000000000000000000000000000000002e300000000000000000000000000000000000000000000000000000000000002e400000000000000000000000000000000000000000000000000000000000002e500000000000000000000000000000000000000000000000000000000000002e600000000000000000000000000000000000000000000000000000000000002e700000000000000000000000000000000000000000000000000000000000002e800000000000000000000000000000000000000000000000000000000000002e900000000000000000000000000000000000000000000000000000000000002ea00000000000000000000000000000000000000000000000000000000000002eb00000000000000000000000000000000000000000000000000000000000002ec00000000000000000000000000000000000000000000000000000000000002ed00000000000000000000000000000000000000000000000000000000000002ee00000000000000000000000000000000000000000000000000000000000002ef00000000000000000000000000000000000000000000000000000000000002f000000000000000000000000000000000000000000000000000000000000002f100000000000000000000000000000000000000000000000000000000000002f200000000000000000000000000000000000000000000000000000000000002f300000000000000000000000000000000000000000000000000000000000002f400000000000000000000000000000000000000000000000000000000000002f500000000000000000000000000000000000000000000000000000000000002f600000000000000000000000000000000000000000000000000000000000002f73700000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c100000000000000000000000000000000000000000000000000000000000003c200000000000000000000000000000000000000000000000000000000000003c300000000000000000000000000000000000000000000000000000000000003c400000000000000000000000000000000000000000000000000000000000003c500000000000000000000000000000000000000000000000000000000000003c600000000000000000000000000000000000000000000000000000000000003c700000000000000000000000000000000000000000000000000000000000003c800000000000000000000000000000000000000000000000000000000000003c900000000000000000000000000000000000000000000000000000000000003ca00000000000000000000000000000000000000000000000000000000000003cb00000000000000000000000000000000000000000000000000000000000003cc00000000000000000000000000000000000000000000000000000000000003cd00000000000000000000000000000000000000000000000000000000000003ce00000000000000000000000000000000000000000000000000000000000003cf00000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d100000000000000000000000000000000000000000000000000000000000003d200000000000000000000000000000000000000000000000000000000000003d300000000000000000000000000000000000000000000000000000000000003d400000000000000000000000000000000000000000000000000000000000003d500000000000000000000000000000000000000000000000000000000000003d600000000000000000000000000000000000000000000000000000000000003d700000000000000000000000000000000000000000000000000000000000003d800000000000000000000000000000000000000000000000000000000000003d900000000000000000000000000000000000000000000000000000000000003da00000000000000000000000000000000000000000000000000000000000003db00000000000000000000000000000000000000000000000000000000000003dc00000000000000000000000000000000000000000000000000000000000003dd00000000000000000000000000000000000000000000000000000000000003de00000000000000000000000000000000000000000000000000000000000003df00000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e100000000000000000000000000000000000000000000000000000000000003e200000000000000000000000000000000000000000000000000000000000003e300000000000000000000000000000000000000000000000000000000000003e400000000000000000000000000000000000000000000000000000000000003e500000000000000000000000000000000000000000000000000000000000003e600000000000000000000000000000000000000000000000000000000000003e700000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000003e900000000000000000000000000000000000000000000000000000000000003ea00000000000000000000000000000000000000000000000000000000000003eb00000000000000000000000000000000000000000000000000000000000003ec00000000000000000000000000000000000000000000000000000000000003ed00000000000000000000000000000000000000000000000000000000000003ee00000000000000000000000000000000000000000000000000000000000003ef00000000000000000000000000000000000000000000000000000000000003f000000000000000000000000000000000000000000000000000000000000003f100000000000000000000000000000000000000000000000000000000000003f200000000000000000000000000000000000000000000000000000000000003f300000000000000000000000000000000000000000000000000000000000003f400000000000000000000000000000000000000000000000000000000000003f500000000000000000000000000000000000000000000000000000000000003f60200000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000004c11000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000006ca00000000000000000000000000000000000000000000000000000000000006c100000000000000000000000000000000000000000000000000000000000006cb00000000000000000000000000000000000000000000000000000000000006c200000000000000000000000000000000000000000000000000000000000006cc00000000000000000000000000000000000000000000000000000000000006c300000000000000000000000000000000000000000000000000000000000006cd00000000000000000000000000000000000000000000000000000000000006c400000000000000000000000000000000000000000000000000000000000006ce00000000000000000000000000000000000000000000000000000000000006c500000000000000000000000000000000000000000000000000000000000006cf00000000000000000000000000000000000000000000000000000000000006c600000000000000000000000000000000000000000000000000000000000006d000000000000000000000000000000000000000000000000000000000000006c700000000000000000000000000000000000000000000000000000000000006d100000000000000000000000000000000000000000000000000000000000006c800000000000000000000000000000000000000000000000000000000000006d200000000000000000000000000000000000000000000000000000000000006c900000000000000000000000000000000000000000000000000000000000006d300000000000000000000000000000000000000000000000000000000000006ca00000000000000000000000000000000000000000000000000000000000006d400000000000000000000000000000000000000000000000000000000000006cb00000000000000000000000000000000000000000000000000000000000006d500000000000000000000000000000000000000000000000000000000000006cc00000000000000000000000000000000000000000000000000000000000006d600000000000000000000000000000000000000000000000000000000000006cd00000000000000000000000000000000000000000000000000000000000006d700000000000000000000000000000000000000000000000000000000000006ce00000000000000000000000000000000000000000000000000000000000006d800000000000000000000000000000000000000000000000000000000000006cf00000000000000000000000000000000000000000000000000000000000006d9000011000000021c000000b00c86563aaf18f39d372daba8a93c1d0523d33332aa8763cddce2c2d6fdf1b533f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb6f8420eb62b2056fc9ba4b629b58c5c54b7927645882de7c98f2b9a7b0da81a610ded805700aa226f61550a89bb37484c5c74c8474bed10bc4723e7e07acfaed0689991f5000000b0243ebdd14aa0c5c42f1a726142cac680d39f781f714e5ba8a108eae428a1fbaffc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb6fc420eb61274702055fae826f528dd56cf9fc7640fc6446ddc3921c48dec4cda489dc6d218628a05fcdcdcb0b3240f04f60371c2fbb955a90deadfbb3ef5d6dd9349d871000000b00bfa610ad4ff82b51dd21bf27b0fd220f197586d7b63833d51897e93d87b4f5b00430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb600430eb62a2cd7b6f182ba4ded15a40f692e70dfbf92895aa300199f521274e7734e0d4e301af19c9864aed7ab10d5bd8f921b3eab859a95d4b1d796031bfeeabdfa1eed0000021c000000b023b2c8a1708754dc15bee2ab149e7b9ca1639d5a422a7b1815afa6a1032b95d704430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb604430eb611e87af07be1773edbcd4da0a1737c7fdd8a69a8ad15413402930897232760fa17d694d622c36bc899c87f4ec7d726dec97d7ae3dec6ff2ab39c929a6dd37299000000b00b06e1c52add86d9555b63ad2cabccbb28fbf9fe8f38026195f3d91a3ddbdc5208430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb608430eb629a0e28717694965d3ba14593b0225fb8d56ae9573dc390ec6b930a44dd7a7762f8efc6cbe4b3def91b546076165d05a7949bfd0a58df70577c2baa79883b915000000b022bf495bc66559004d482a65c63a7636d8c83eeb55fefa3c5a1a0127688c22ce0c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb60c430eb610f4fbaad1bf7b631356955b530f771a14ef0b39c0e9c05846fd631d8887edf116e3159078a16fecd151c7097973217900e21c74f29b7e4ef806ed20d333ff900000021c000000b00a13627f80bb8afd8ce4ab67de47c75560609b8fa30c8185da5e33a0a33c694910430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb610430eb628ad63416d474d8a0b435c13ec9e2095c4bb502687b0b8330b238b2ab338346d2e9b7d2714294213c93e8dc21301caf4b0ae6161b9627629bc2d152dfde4460c000000b021cbca161c435d2484d1722077d670d1102ce07c69d379609e845badcdecafc514430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb614430eb610017c65279d7f874adfdd1604ab71b44c53accad4be3f7c8b67bda3ede87ae815ef964ace7f741108db0ec42b0f1c133846be06066ffd733c7147a738948c87000000b0091fe339d6998f21c46df3228fe3c1ef97c53d20b6e100aa1ec88e27089cf64018430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb618430eb627b9e3fbc32551ae42cca3ce9e3a1b2ffc1ff1b79b8537574f8de5b11898c1642da7fde16a07463800c7d57cc49dc58ee81302f2cd36f54e00976fb46344d3030000021c000000b020d84ad072216148bc5ab9db29726b6b4791820d7da7f884e2eeb634334d3cbc1c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb61c430eb60f0dfd1f7d7b83ab826924d0b6476c4e83b84e5be892bea0cfd2182a534907df14fc1705245d78354064567edcab16ad6fab5f971a447c9780dba22d9df5197e000000b0082c63f42c779345fbf73add417fbc89cf29deb1cab57fce6332e8ad6dfd833720430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb620430eb626c664b6190355d27a55eb894fd615ca33849348af59b67b93f840377df94e5b2cb47e9bbfe54a5c38511d377639c0291f77a483e10b74724501ca3ac8a55ffa000000b01fe4cb8ac7ff656cf3e40195db0e66057ef6239e917c77a9275910ba98adc9b324430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb624430eb60e1a7dd9d35987cfb9f26c8b67e366e8bb1cefecfc673dc5143c72b0b8a994d6140897bf7a3b7c5977ed9e398e471147a71001282e18fbbbc545fcb40355a6750000021c000000b00738e4ae8255976a33808297f31bb724068e8042de89fef2a79d4333d35e102e28430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb628430eb625d2e5706ee159f6b1df3344017210646ae934d9c32e359fd8629abde359db522bc0ff5615c34e806fda64f227d5bac356dc4614f4dff396896c24c12e05ecf1000000b01ef14c451ddd69912b6d49508caa609fb65ac52fa550f6cd6bc36b40fe0e56aa2c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb62c430eb60d26fe9429378bf3f17bb446197f6182f281917e103bbce958a6cd371e0a21cd13151879d019807daf76e5f43fe30be1de74a2b941ed7ae009b0573a68b6336c000000b006456568d8339b8e6b09ca52a4b7b1be3df321d3f25e7e16ec079dba38be9d2530430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb630430eb624df662ac4bf5e1ae9687afeb30e0afea24dd66ad702b4c41cccf54448ba68492acd80106ba152a4a763acacd971b55d8e40e7a608b472bacdd67f47936679e80000021c000000b01dfdccff73bb6db562f6910b3e465b39edbf66c0b92575f1b02dc5c7636ee3a134430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb634430eb60c337f4e7f1590182904fc00cb1b5c1d29e6330f24103c0d9d1127bd836aaec41221993425f784a1e7002daef17f067c15d9444a55c1fa044e1ab1c0ce16c063000000b00551e6232e119fb2a293120d5653ac587557c3650632fd3b3071f8409e1f2a1c38430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb638430eb623ebe6e51a9d623f20f1c2b964aa0598d9b277fbead733e861374fcaae1af54029da00cac17f56c8deecf4678b0daff7c5a589371c88f1df1240d9cdf8c706df000000b01d0a4db9c99971d99a7fd8c5efe255d425240851ccf9f515f498204dc8cf70983c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb63c430eb60b400008d4f3943c608e43bb7cb756b7614ad4a037e4bb31e17b8243e8cb3bbb112e19ee7bd588c61e897569a31b01164d3de5db6996792892850c4733774d5a0000021c000000b0045e66dd83efa3d6da1c59c807efa6f2acbc64f61a077c5f74dc52c7037fb71340430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb640430eb622f8679f707b6663587b0a74164600331117198cfeabb30ca5a1aa51137b823728e68185175d5aed16763c223ca9aa91fd0a2ac8305d710356ab34545e2793d6000000b01c16ce741f7775fdd2092080a17e506e5c88a9e2e0ce743a39027ad42e2ffd8f44430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb644430eb60a4c80c32ad1986098178b762e53515198af76314bb93a5625e5dcca4e2bc8b2103a9aa8d1b38cea5612bd2454b6fbb084a2876c7d6af84cd6ef66cd98d7da51000000b0036ae797d9cda7fb11a5a182b98ba18ce42106872ddbfb83b946ad4d68e0440a48430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb648430eb62204e859c6596a879004522ec7e1facd487bbb1e12803230ea0c04d778dc0f2e27f3023f6d3b5f114dff83dcee45a52c346ecc594431f0279b158edac38820cd0000021c000000b01b234f2e75557a220992683b531a4b0893ed4b73f4a2f35e7d6cd55a93908a864c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb64c430eb60959017d80af9c84cfa0d330dfef4bebd01417c25f8db97a6a503750b38c55a90f471b632791910e8d9c04df0652f64abc0728fd913f77711b59c153fe386748000000b0027768522fabac1f492ee93d6b279c271b85a81841b07aa7fdb107d3ce40d10150430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb650430eb6211169141c376eabc78d99e9797df5677fe05caf2654b1552e765f5dde3c9c2526ff82f9c31963358588cb979fe19fc66bd36dea58066f4bdf7fe96128e8adc4000000b01a2fcfe8cb337e46411baff604b645a2cb51ed0508777282c1d72fe0f8f1177d54430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb654430eb608658237d68da0a9072a1aeb918b46860778b9537362389eaeba91d718ece2a00e539c1d7d6f9532c5254c99b7eef0e4f36bca8ea513f6955fc41bda6398f43f00000fa400000168000000b00183e90c8589b04380b830f81cc396c152ea49a95584f9cc421b625a33a15df858430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb658430eb6201de9ce721572cfff16e1a42b19f001b744fe403a29307972e0b9e4439d291c260c03b418f76759bd121352517d9a60a3380f7b6bdaee7023ea43e78e493abb000000b0193c50a32111826a78a4f7b0b652403d02b68e961c4bf1a706418a675e51a4745c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb65c430eb6077202f22c6ba4cd3eb362a6432741203edd5ae48736b7c2f324ec5d7e4d6f970d601cd7d34d9956fcae9454698aeb7f2ad06c1fb8e875b9a42e7660c8f9813600000168000000b0009069c6db67b467b84178b2ce5f915b8a4eeb3a695978f08685bce09901eaef60430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb660430eb61f2a6a88c7f376f436a0295edcb5ea9beea99fd14dfdaf9db74b146aa8fdb6132518846e6ed56b7df49b5b0d031994fada9cb10c7faf6d9468549e6df3a9c7b2000000b01848d15d76ef868eb02e3f6b67ee3ad73a1b3027302070cb4aabe4edc3b2316b64430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb664430eb6067e83ac8249a8f1763caa60f4c33bba7641fc759b0b36e7378f46e3e3adfc8e0c6c9d92292b9d7b3437dc0f1b26e61962350db0ccbcf4dde898d0e72e5a0e2d00000168000000b0300138f4127758b5a81b0624017ce452e9e77513f6e768a60ed20cfaee6277e768430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb668430eb61e36eb431dd17b186e2971198e51e536260e416261d22ec1fbb56ef10e5e430a24250528c4b36fa22c24a2c7b4b58f951201529d9383ecb8acbef8f4590a54a9000000b017555217cccd8ab2e7b78726198a3571717fd1b843f4efef8f163f742912be626c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb66c430eb6058b0466d827ad15adc5f21ba65f3654ada69e06aedfb60b7bf9a16a490e89850b791e4c7f09a19f6bc123c9ccc2e0b39999af41e09174022d032b6d93ba9b2400000168000000b02f0db9ae68555cd9dfa44ddeb318deed214c16a50abbe7ca533c678153c304de70430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb670430eb61d436bfd73af7f3ca5b2b8d43feddfd05d72e2f375a6ade6401fc97773bed001233185e31a9173c663adea8266518a2f4965f42ea7586bdcf129537abe6ae1a0000000b01661d2d222ab8ed71f40cee0cb26300ba8e4734957c96f13d38099fa8e734b5974430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb674430eb6049785212e05b139e54f39d657fb30eee50b3f97c2b4352fc063fbf0ae6f167c0a859f06d4e7a5c3a34a6b847e5edb4dd0fe50d2f465f326716d85f3f91b281b00000168000000b02e1a3a68be3360fe172d959964b4d98758b0b8361e9066ee97a6c207b92391d578430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb678430eb61c4fecb7c98d8360dd3c008ef189da6a94d78484897b2d0a848a23fdd91f5cf8223e069d706f77ea9b37323d17ed84c980ca95bfbb2ceb013593ae0123cb6e97000000b0156e538c788992fb56ca169b7cc22aa5e04914da6b9dee3817eaf480f3d3d8507c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb67c430eb603a405db83e3b55e1cd8819109972b891c6fe128d688b45404ce567713cfa37309921fc12ac5a9e7dad3b33f2ffad5e80862f264083a724ab5d7e07a5e7bb51200000168000000b02d26bb23141165224eb6dd541650d421901559c73264e612dc111c8e1e841ecc80430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb680430eb61b5c6d721f6b878514c54849a325d504cc3c26159d4fac2ec8f47e843e7fe9ef214a8757c64d7c0ed2c079f7c9897f63b82f3750cf016a2579fe0887892bfb8e000000b0147ad446ce67971f8e535e562e5e254017adb66b7f726d5c5c554f075934654784430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb684430eb602b08695d9c1b9825461c94bbb33262353d482b9ea5d33784938b0fd7930306a089ea07b80a3ae0c125cfaf9e196d0823fc793f51c0ef16efa423b00c3dc420900000168000000b02c333bdd69ef69468640250ec7eccebbc779fb5846396537207b771483e4abc388430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb688430eb61a68ee2c75498ba94c4e900454c1cf9f03a0c7a6b1242b530d5ed90aa3e076e6205708121c2b80330a49c1b27b2579fdef93d8e1e2d5e949be68630dee8c8885000000b01387550124459b43c5dca610dffa1fda4f1257fc9346ec80a0bfa98dbe94f23e8c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb68c430eb601bd07502f9fbda68beb11066ccf20bd8b39244afe31b29c8da30b83de90bd6107ab2135d681b23049e642b49332cb1c772c35862fe370933eac9587293ccf0000000168000000b02b3fbc97bfcd6d6abdc96cc97988c955fede9ce95a0de45b64e5d19ae94538ba90430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb690430eb619756ee6cb278fcd83d7d7bf065dca393b056937c4f8aa7751c93391094103dd1f6388cc7209845741d3096d2cc1749826f87a72f6aa686e02d2bd9453ed157c000000b01293d5bb7a239f67fd65edcb91961a748676f98da71b6ba4e52a041423f57f3594430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb694430eb600c9880a857dc1cac37458c11e6b1b57c29dc5dc120631c0d20d660a43f14a5806b7a1f02c5fb654816f8a6f44cec5b6ae90d71743b7efb78316f00d8e9d5bf700000168000000b02a4c3d5215ab718ef552b4842b24c3f036433e7a6de2637fa9502c214ea5c5b198430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb698430eb61881efa1210593f1bb611f79b7f9c4d3726a0ac8d8cd299b96338e176ea190d41e700986c7e7887b795c5127de5d6f325e5d1c040a7ee792473d181ab94da273000000b011a05675d001a38c34ef35864332150ebddb9b1ebaefeac929945e9a89560c2c9c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb69c430eb6303a5737bc8d6618b34de63251886e4f22364fb59f9421765a59b6249951d75005c422aa823dba78b8f8d229f66ac050e5f578a8578c6edbc7814a93f3fde8ee00000168000000b02958be0c6b8975b32cdbfc3edcc0be8a6da7e00b81b6e2a3edba86a7b40652a8a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6a0430eb6178e705b76e39815f2ea67346995bf6da9ceac59eca1a8bfda9de89dd4021dcb1d7c8a411dc58c9fb0e598e28ff969cc95c1bd951e5366b68ba772a11eae2f6a000000b010acd73025dfa7b06c787d40f4ce0fa8f5403cafcec469ed6dfeb920eeb69923a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb6a4430eb62f46d7f2126b6a3cead72ded032468e9599af146b368a09a9ec410aafeb2644704d0a364d81bbe9cf08219e4a806baeb1d5a1a396b60ee000beba51a595e75e500000168000000b028653ec6c16779d7646543f98e5cb924a50c819c958b61c83224e12e1966df9fa8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6a8430eb6169af115ccc19c3a2a73aeef1b31ba07e1334deb007627e41f0843243962aac21c890afb73a390c3e86ee09d41956466cd265f263227e5dad011cd27840ebc61000000b00fb957ea7bbdabd4a401c4fba66a0a432ca4de40e298e911b26913a75417261aac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb6ac430eb62e5358ac68496e61226075a7b4c0638390ff92d7c73d1fbee32e6b316412f13e03dd241f2df9c2c1280b619f59a2b58554bebbca7f356d245055ffa0bebf02dc380000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030100000000000000000000000000000000000000000000000000000000000003020000000000000000000000000000000000000000000000000000000000000303000000000000000000000000000000000000000000000000000000000000030400000000000000000000000000000000000000000000000000000000000003050000000000000000000000000000000000000000000000000000000000000306000000000000000000000000000000000000000000000000000000000000030700000000000000000000000000000000000000000000000000000000000003080000000000000000000000000000000000000000000000000000000000000309000000000000000000000000000000000000000000000000000000000000030a000000000000000000000000000000000000000000000000000000000000030b000000000000000000000000000000000000000000000000000000000000030c000000000000000000000000000000000000000000000000000000000000030d000000000000000000000000000000000000000000000000000000000000030e000000000000000000000000000000000000000000000000000000000000030f0000000000000000000000000000000000000000000000000000000000000310000000000000000000000000000000000000000000000000000000000000031100000000000000000000000000000000000000000000000000000000000003120000000000000000000000000000000000000000000000000000000000000313000000000000000000000000000000000000000000000000000000000000031400000000000000000000000000000000000000000000000000000000000003150000000000000000000000000000000000000000000000000000000000000316000000000000000000000000000000000000000000000000000000000000031700000000000000000000000000000000000000000000000000000000000003180000000000000000000000000000000000000000000000000000000000000319000000000000000000000000000000000000000000000000000000000000031a000000000000000000000000000000000000000000000000000000000000031b000000000000000000000000000000000000000000000000000000000000031c000000000000000000000000000000000000000000000000000000000000031d000000000000000000000000000000000000000000000000000000000000031e000000000000000000000000000000000000000000000000000000000000031f0000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032100000000000000000000000000000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000323000000000000000000000000000000000000000000000000000000000000032400000000000000000000000000000000000000000000000000000000000003250000000000000000000000000000000000000000000000000000000000000326000000000000000000000000000000000000000000000000000000000000032700000000000000000000000000000000000000000000000000000000000003280000000000000000000000000000000000000000000000000000000000000329000000000000000000000000000000000000000000000000000000000000032a000000000000000000000000000000000000000000000000000000000000032b000000000000000000000000000000000000000000000000000000000000032c000000000000000000000000000000000000000000000000000000000000032d000000000000000000000000000000000000000000000000000000000000032e000000000000000000000000000000000000000000000000000000000000032f00000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000331000000000000000000000000000000000000000000000000000000000000033200000000000000000000000000000000000000000000000000000000000003330000000000000000000000000000000000000000000000000000000000000334000000000000000000000000000000000000000000000000000000000000033500000000000000000000000000000000000000000000000000000000000003360000000000000000000000000000000000000000000000000000000000000337370000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040100000000000000000000000000000000000000000000000000000000000004020000000000000000000000000000000000000000000000000000000000000403000000000000000000000000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000000004050000000000000000000000000000000000000000000000000000000000000406000000000000000000000000000000000000000000000000000000000000040700000000000000000000000000000000000000000000000000000000000004080000000000000000000000000000000000000000000000000000000000000409000000000000000000000000000000000000000000000000000000000000040a000000000000000000000000000000000000000000000000000000000000040b000000000000000000000000000000000000000000000000000000000000040c000000000000000000000000000000000000000000000000000000000000040d000000000000000000000000000000000000000000000000000000000000040e000000000000000000000000000000000000000000000000000000000000040f0000000000000000000000000000000000000000000000000000000000000410000000000000000000000000000000000000000000000000000000000000041100000000000000000000000000000000000000000000000000000000000004120000000000000000000000000000000000000000000000000000000000000413000000000000000000000000000000000000000000000000000000000000041400000000000000000000000000000000000000000000000000000000000004150000000000000000000000000000000000000000000000000000000000000416000000000000000000000000000000000000000000000000000000000000041700000000000000000000000000000000000000000000000000000000000004180000000000000000000000000000000000000000000000000000000000000419000000000000000000000000000000000000000000000000000000000000041a000000000000000000000000000000000000000000000000000000000000041b000000000000000000000000000000000000000000000000000000000000041c000000000000000000000000000000000000000000000000000000000000041d000000000000000000000000000000000000000000000000000000000000041e000000000000000000000000000000000000000000000000000000000000041f0000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000042100000000000000000000000000000000000000000000000000000000000004220000000000000000000000000000000000000000000000000000000000000423000000000000000000000000000000000000000000000000000000000000042400000000000000000000000000000000000000000000000000000000000004250000000000000000000000000000000000000000000000000000000000000426000000000000000000000000000000000000000000000000000000000000042700000000000000000000000000000000000000000000000000000000000004280000000000000000000000000000000000000000000000000000000000000429000000000000000000000000000000000000000000000000000000000000042a000000000000000000000000000000000000000000000000000000000000042b000000000000000000000000000000000000000000000000000000000000042c000000000000000000000000000000000000000000000000000000000000042d000000000000000000000000000000000000000000000000000000000000042e000000000000000000000000000000000000000000000000000000000000042f00000000000000000000000000000000000000000000000000000000000004300000000000000000000000000000000000000000000000000000000000000431000000000000000000000000000000000000000000000000000000000000043200000000000000000000000000000000000000000000000000000000000004330000000000000000000000000000000000000000000000000000000000000434000000000000000000000000000000000000000000000000000000000000043500000000000000000000000000000000000000000000000000000000000004360200000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000501100000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000070a0000000000000000000000000000000000000000000000000000000000000701000000000000000000000000000000000000000000000000000000000000070b0000000000000000000000000000000000000000000000000000000000000702000000000000000000000000000000000000000000000000000000000000070c0000000000000000000000000000000000000000000000000000000000000703000000000000000000000000000000000000000000000000000000000000070d0000000000000000000000000000000000000000000000000000000000000704000000000000000000000000000000000000000000000000000000000000070e0000000000000000000000000000000000000000000000000000000000000705000000000000000000000000000000000000000000000000000000000000070f00000000000000000000000000000000000000000000000000000000000007060000000000000000000000000000000000000000000000000000000000000710000000000000000000000000000000000000000000000000000000000000070700000000000000000000000000000000000000000000000000000000000007110000000000000000000000000000000000000000000000000000000000000708000000000000000000000000000000000000000000000000000000000000071200000000000000000000000000000000000000000000000000000000000007090000000000000000000000000000000000000000000000000000000000000713000000000000000000000000000000000000000000000000000000000000070a0000000000000000000000000000000000000000000000000000000000000714000000000000000000000000000000000000000000000000000000000000070b0000000000000000000000000000000000000000000000000000000000000715000000000000000000000000000000000000000000000000000000000000070c0000000000000000000000000000000000000000000000000000000000000716000000000000000000000000000000000000000000000000000000000000070d0000000000000000000000000000000000000000000000000000000000000717000000000000000000000000000000000000000000000000000000000000070e0000000000000000000000000000000000000000000000000000000000000718000000000000000000000000000000000000000000000000000000000000070f0000000000000000000000000000000000000000000000000000000000000719000011000000021c000000b02771bf8117457dfb9bee8bb43ff8b3bedc71232da95fe0ec768f3bb47ec76c96b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb6b0430eb615a771d0229fa05e61fcf6a9cccdb4a21897ef7c144aa70863729daa9ec337b91b958bb5c98194e81ff82857f3315f01048b00b745fc64ff147c27ade96f4958000000b00ec5d8a4d19baff8db8b0cb6580604dd64097fd1f66d6835f6d36e2db977b311b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb6b4430eb62d5fd966be27728559e9bd62665c5e1dc8643468db119ee32798c5b7c9737e3502e9a4d983d7c6e55f94a95a0b3eb01f8c235d5b9309ec4894c05a27241f8fd3000000b0267e403b6d23821fd377d36ef194ae5913d5c4bebd346010baf9963ae427f98db8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb6b8430eb614b3f28a787da48299863e647e69af3c4ffc910d281f262ca7dcf8310423c4b01aa20c701f5f990c57817012a4cd599b3befa24859d0e42358e682344ecfd64f0000021c000000b00dd2595f2779b41d1314547109a1ff779b6e21630a41e75a3b3dc8b41ed84008bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb6bc430eb62c6c5a21140576a99173051d17f858b7ffc8d5f9eee61e076c03203e2ed40b2c01f62593d9b5cb09971df114bcdaaab9c387feeca6de6b6cd92ab4ad89801cca000000b0258ac0f5c30186440b011b29a330a8f34b3a664fd108df34ff63f0c149888684c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb6c0430eb613c07344ce5ba8a6d10f861f3005a9d68761329e3bf3a550ec4752b7698451a719ae8d2a753d9d308f0ab7cd56695435735443d96da563479d50dcbab4306346000000b00cdeda197d57b8414a9d9c2bbb3dfa11d2d2c2f41e16667e7fa8233a8438ccffc4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb6c4430eb62b78dadb69e37acdc8fc4cd7c9945352372d778b02ba9d2bb06d7ac4943498230102a64e2f93cf2dcea738cf6e76a553faeca07dbab2ea911d950f33eee0a9c10000021c000000b0249741b018df8a68428a62e454cca38d829f07e0e4dd5e5943ce4b47aee9137bc8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb6c8430eb612ccf3ff2439accb0898cdd9e1a1a470bec5d42f4fc8247530b1ad3dcee4de9e18bb0de4cb1ba154c693ff8808054ecfaab8e56a8179e26be1bb37411990f03d000000b00beb5ad3d335bc658226e3e66cd9f4ac0a37648531eae5a2c4127dc0e99959f6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb6cc430eb62a855b95bfc17ef2008594927b304dec6e92191c168f1c4ff4d7d54af995251a000f27088571d3520630808a20129fee3251420ece8769b561ff69ba544136b8000000b023a3c26a6ebd8e8c7a13aa9f06689e27ba03a971f8b1dd7d8838a5ce1449a072d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb6d0430eb611d974b97a17b0ef40221594933d9f0af62a75c0639ca399751c07c434456b9517c78e9f20f9a578fe1d4742b9a14969e21d86fb954e6190262591c77ef17d340000021c000000b00af7db8e2913c089b9b02ba11e75ef46419c061645bf64c7087cd8474ef9e6edd4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb6d4430eb62991dc50159f8316380edc4d2ccc4886a5f6baad2a639b7439422fd15ef5b2112f7ff635bc81779ff60a0dfb532ff2e591e9cbe85c15596aea4bb9d4a9a1c3b0000000b022b04324c49b92b0b19cf259b80498c1f1684b030c865ca1cca3005479aa2d69d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb6d8430eb610e5f573cff5b51377ab5d4f44d999a52d8f1751777122bdb986624a99a5f88c16d40f5976d7a99d35a68efd6b3d44041982288ca922e0b46a8fec4de4520a2b000000b00a045c487ef1c4adf139735bd011e9e07900a7a75993e3eb4ce732cdb45a73e4dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6dc430eb6289e5d0a6b7d873a6f982407de684320dd5b5c3e3e381a987dac8a57c4563f082e8c76f0125f7bc42d9355b604cbed7fc94e6d796fe9d88f2eb6145b0f0250a70000021c000000b021bcc3df1a7996d4e9263a1469a0935c28ccec94205adbc6110d5adadf0aba60e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb6e0430eb60ff2762e25d3b937af34a509f675943f64f3b8e28b45a1e1fdf0bcd0ff06858315e09013ccb5adc16d2fd6b81cd93e9e50e6ca1dbcf75fd8aefa46d449b29722000000b00910dd02d4cfc8d228c2bb1681ade47ab06549386d68630f91518d5419bb00dbe4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb6e4430eb627aaddc4c15b8b5ea7216bc290043dbb14bffdcf520c99bcc216e4de29b6cbff2d98f7aa683d7fe8651c9d70b667e81a00b30f0a83be57b373206ee17462dd9e000000b020c9449970579af920af81cf1b3c8df660318e25342f5aea5577b561446b4757e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb6e8430eb60efef6e87bb1bd5be6bdecc4a8118ed99c585a739f1a2106425b17576467127a14ed10ce2293b1e5a4b91e72ce753938884b6baed0cbdefcf364a15aaf1324190000021c000000b0081d5dbd2aadccf6604c02d13349df14e7c9eac9813ce233d5bbe7da7f1b8dd2ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb6ec430eb626b75e7f17398f82deaab37d41a038554c249f6065e118e106813f648f1758f62ca57864be1b840c9ca5e52b6803e2b43817b09b9792d6d7b78ac967d9c36a95000000b01fd5c553c6359f1d5838c989ccd8889097962fb64803da0e99e20fe7a9cbd44ef0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb6f0430eb60e0b77a2d18fc1801e47347f59ad8973d3bcfc04b2eea02a86c571ddc9c79f7113f991887871b609dc42662d801133d2bfb00d3fe4a05e2137cefbe11473b110000000b00729de77808bd11a97d54a8be4e5d9af1f2e8c5a951161581a264260e47c1ac9f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb6f4430eb625c3df396d1793a71633fb37f33c32ef838940f179b598054aeb99eaf477e5ed2bb1f91f13f98830d42f2ce6199fdd4e6f7c522cab6755fbfbf523ee3f23f78c0000021c000000b01ee2460e1c13a3418fc211447e74832acefad1475bd85932de4c6a6e0f2c6145f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb6f8430eb60d17f85d276dc5a455d07c3a0b49840e0b219d95c6c31f4ecb2fcc642f282c6813061242ce4fba2e13cbade831ad2e6cf714aed0f874dd457c39566779d43e07000000b006365f31d669d53ecf5e92469681d44956932deba8e5e07c5e909ce749dca7c0fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb6fc430eb624d05ff3c2f597cb4dbd42f2a4d82d89baede2828d8a17298f55f47159d872e42abe79d969d78c550bb874a0cb3bd7e8a6e0f3bdbf3bd520405f7e74a4848483000000b01e5650de41fa32597666818e504838469cbef6822cb478a252f3262ae9b5fb6d00440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb600440eb60c2479177d4bc9c88d59c3f4bce57ea842863f26da979e730f9a26ea9488b95f121292fd242dbe524b54f5a2e34929072e7950620c495c69c0a3b0eddf34cafe0000021c000000b005aa6a01fc506456b6030290685589652457532679c1ffebd33758a4246641e804440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb604440eb624446ac3e8dc26e33461b33c76abe2a588b207bd5e66369903fcb02e34620d0c2a3284a98fbe1b6cf25ce4ea9d0f8d0474a518f89017f48fb5063a317f0e1eab000000b01d62d19897d8367dadefc94901e432e0d42398134088f7c6975d80b14f16886408440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb608440eb60b9883e7a33258e073fe343e8eb933c4104a6461ab73bde28440e2a76f12538711869dcd4a144d6a31f965ecb51cde22fc3d759cdd257bd9354a6caab9be6526000000b004b6eabc522e687aed8c4a4b19f183ff5bbbf4b78d967f1017a1b32a89c6cedf0c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb60c440eb62350eb7e3eba2b076beafaf72847dd3fc016a94e723ab5bd48670ab499c29a03293f0563e59c1f9129e62ca54eab879eac09ba89a3ec73b3f97094b7e46eaba200000fa400000168000000b01c6f5252edb63aa1e5791103b3802d7b0b8839a4545d76eadbc7db37b477155b10440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb610440eb60aa504a1f9105d04ab877bf940552e5e47af05f2bf483d06c8ab3d2dd472e07e10931e879ff2518e6982ada766b8d8bd33a2172df0f9fafd79b4c7311f1ef21d000000b003c36b76a80c6c9f25159205cb8d7e9993209648a16afe345c0c0db0ef275bd614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb614440eb6225d6c3894982f2ba37442b1d9e3d7d9f77b4adf860f34e18cd1653aff2326fa284b861e3b7a23b5616f746000478238e36e5c1ab7c0f2d83ddaef3e49cf389900000168000000b01b7bd30d43943ec61d0258be651c281542ecdb356831f60f203235be19d7a25218440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb618440eb609b1855c4eee6128e310c3b3f1f128f87f13a783d31cbc2b0d1597b439d36d750f9f9f41f5d055b2a10bf5621854d3576b06b8bf04ce7a21be1f21b7847f7f14000000b002cfec30fdea70c35c9ed9c07d297933ca8537d9b53f7d58a07668375487e8cd1c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb61c440eb62169ecf2ea76334fdafd8a6c8b7fd2742edfec7099e3b405d13bbfc16483b3f1275806d8915827d998f8bc1ab1e37cd31ad2fdabcb9571fc824549c4af2fc59000000168000000b01a8853c7997242ea548ba07916b822af7a517cc67c067533649c90447f382f4920440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb620440eb608be0616a4cc654d1a9a0b6ea38d2392b6784914e6f13b4f517ff23a9f33fa6c0eac1ffc4bae59d6d8953d1cc9f0cdf1a26b5a5018a2f94602897c3de9e00c0b000000b001dc6ceb53c874e79428217b2ec573ce01e9d96ac913fc7ce4e0c2bdb9e875c424440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb624440eb620766dad405437741286d2273d1bcd0e66448e01adb8332a15a61a47c9e440e826648792e7362bfdd08203d5637f776d52379f3cdf69f120c6afa44b1490528700000168000000b01994d481ef50470e8c14e833c8541d49b1b61e578fdaf457a906eacae498bc4028440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb628440eb607ca86d0faaa69715223532955291e2ceddceaa5fac5ba7395ea4cc1049487630db8a0b6a18c5dfb101e84d77b8cc88bd9cffbe12c77786a46f3d6c44f409902000000b000e8eda5a9a6790bcbb16935e0616e68394e7afbdce87ba1294b1d441f4902bb2c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb62c440eb61f82ee6796323b984a1019e1eeb7c7a89da92f92c18cb24e5a1074ce2f44cddf2571084d3d143022080b4b90151b7207899c40cdf33e70450b19fed179f0df7e00000168000000b018a1553c452e4b32c39e2fee79f017e3e91abfe8a3af737bed71455149f9493730440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb630440eb606d7078b50886d9589ac9ae406c518c725418c370e9a3997da54a74769f5145a0cc52170f76a621f47a7cc922d28c32611349d72404bf78e8b5e314ab4a125f9000000b03059bcd2e0b61d59bb8af6a7137ec15f98e704d56a766b56b1976d5e74a98fb334440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb634440eb61e8f6f21ec103fbc8199619ca053c242d50dd123d56131729e7acf5494a55ad6247d890792f234463f94934ac6b76ca1c100e25f0712ef694f845957df516c7500000168000000b017add5f69b0c4f56fb2777a92b8c127e207f6179b783f2a031db9fd7af59d62e38440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb638440eb605e38845a66671b9c135e29eb86113615ca62dc8226eb8bc1ebf01cdcf55a1510bd1a22b4d4866437f31144cdec4bdc048993f03542076b2cfc88bd11a01b2f0000000b02f663d8d3694217df3143e61c51abbf9d04ba6667e4aea7af601c7e4da0a1caa3c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb63c440eb61d9befdc41ee43e0b922a95751efbcdd0c7272b4e935b096e2e529dafa05e7cd238a09c1e8d0386a771ddb057853673bf86583f01ae76e8d93eeb3de44b1f96c00000168000000b016ba56b0f0ea537b32b0bf63dd280d1857e4030acb5871c47645fa5e14ba632540440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb640440eb604f008fffc4475ddf8bf2a5969fd0dfb940acf59364337e063295c5434b62e480ade22e5a3266a67b6ba5c079060b85a7ffde09467f4f5d71432e6577f623fe7000000b02e72be478c7225a22a9d861c76b6b69407b047f7921f699f3a6c226b3f6aa9a144440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb644440eb61ca8709697cc4804f0abf112038bb77743d71445fd0a2fbb274f84615f6674c422968a7c3eae3c8eaea722c029ef61d62fca25812ebbedb1d8590e64aa12866300000168000000b015c6d76b46c8579f6a3a071e8ec407b28f48a49bdf2cf0e8bab054e47a1af01c48440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb648440eb603fc89ba52227a02304872141b990895cb6f70ea4a17b704a793b6da9a16bb3f09eaa39ff9046e8bee43a3c241fcb2f4b76282257bc974fb589d40dde4c2ccde000000b02d7f3f01e25029c66226cdd72852b12e3f14e988a5f3e8c37ed67cf1a4cb36984c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb64c440eb61bb4f150edaa4c29283538ccb527b2117b3bb5d710deaedf6bb9dee7c4c701bb21a30b36948c40b2e6306a7adb8b5c70672ec71242906cd61cc368eb0f73135a00000168000000b014d358259ca65bc3a1c34ed94060024cc6ad462cf301700cff1aaf6adf7b7d1350440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb650440eb603090a74a8007e2667d1b9cecd35033002d4127b5dec3628ebfe1160ff77483608f7245a4ee272b025cceb7cf398ad8eeec723b68f9df41f9d079b644a2359d5000000b02c8bbfbc382e2dea99b01591d9eeabc876798b19b9c867e7c340d7780a2bc38f54440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb654440eb61ac1720b4388504d5fbe808766c3acabb2a0576824b32e03b024396e2a278eb220af8bf0ea6a44d71db9b2358d27570a9e9368a35664ebfa612dc37174d3a05100000168000000b013dfd8dff2845fe7d94c9693f1fbfce6fe11e7be06d5ef31438509f144dc0a0a58440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb658440eb602158b2efdde824a9f5b01897ed0fdca3a38b40c71c0b54d30686be764d7d52d0803a514a4c076d45d563337a534a829262bc547a3727343e171f5eaaf83e6cc000000b02b9840768e0c320ed1395d4c8b8aa662adde2caacd9ce70c07ab31fe6f8c50865c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb65c440eb619cdf2c5996654719747c842185fa745ea04f8f93887ad27f48e93f48f881ba91fbc0cab404848fb5542f9f03ec351a4d5f80a346a396b1ea5981df7da342d4800000168000000b012ec599a4862640c10d5de4ea397f7813576894f1aaa6e5587ef6477aa3c970160440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb660440eb601220be953bc866ed6e44944306cf864719d559d8595347174d2c66dca386224071025cefa9e7af894df7af256d0a2c35d9066d8b746f26825dc507114e473c3000000b02aa4c130e3ea363308c2a5073d26a0fce542ce3be17166304c158c84d4ecdd7d64440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb664440eb618da737fef445895ced10ffcc9fba1e021699a8a4c5c2c4c38f8ee7af4e8a8a01ec88d6596264d1f8ccc41aaf05f4c3f0d5cabc57e0dea42ea02787e3f94ba3f", "txsEffectsHash": "0x80dc9d246537063813894f7edb41c694fbd803946807e6e3e875a6bbd91b531b", "decodedHeader": { "contentCommitment": { - "inHash": "0x8e7d8bf0ef7ebd1607cc7ff9f2fbacf4574ee5b692a5a5ac1e7b1594067b9049", + "inHash": "0x2673dd78c65e0745b5000b70dcda092ae5aa3a7ab292eaa4bd01f1f4f22039a4", "outHash": "0x3c00faec8dc481e71433eb21f1dd016134bf403950c146783ba1928cddcb315d", "txTreeHeight": 2, "txsEffectsHash": "0x80dc9d246537063813894f7edb41c694fbd803946807e6e3e875a6bbd91b531b" @@ -65,19 +47,19 @@ "globalVariables": { "blockNumber": 2, "chainId": 31337, - "timestamp": 1710325361, + "timestamp": 1710506375, "version": 1, "coinbase": "0xa43e0eb6a43e0eb6a43e0eb6a43e0eb6a43e0eb6", "feeRecipient": "0x15a9c4f4c75d79ce22330ca2cdb6c1a6ede1f6d94ba28016eeb25e5578913ccb" }, "lastArchive": { "nextAvailableLeafIndex": 2, - "root": "0x002112631bea3a8334e954f4de111c9158cafeab265fc94ee695b3b4d20f0427" + "root": "0x23464816bf0c3a39a98fcfba3153cdc11638cf5ac8fa184581a066f109dbadf8" }, "stateReference": { "l1ToL2MessageTree": { "nextAvailableLeafIndex": 32, - "root": "0x06c76caee115a61eeb6788977c68a3bea359061b678a1a4f5ffde13e0451717b" + "root": "0x2fdcd19872e0cfe7bdc83a7fd73e581a0a9f8d61ffc575efb15f35d93d2138fa" }, "partialStateReference": { "noteHashTree": { @@ -95,8 +77,7 @@ } } }, - "header": "0x002112631bea3a8334e954f4de111c9158cafeab265fc94ee695b3b4d20f042700000002000000000000000000000000000000000000000000000000000000000000000280dc9d246537063813894f7edb41c694fbd803946807e6e3e875a6bbd91b531b8e7d8bf0ef7ebd1607cc7ff9f2fbacf4574ee5b692a5a5ac1e7b1594067b90493c00faec8dc481e71433eb21f1dd016134bf403950c146783ba1928cddcb315d06c76caee115a61eeb6788977c68a3bea359061b678a1a4f5ffde13e0451717b00000020023ef973dbaa366409f7a01a4ced696227685ce75e57b510d0e7015ebfa72c5000000200231b77b7e0311a71fae5cec0f0281816950f94a24bfc2e67c5ae8619c6ed4c88000002802ae3a1bf2752c8c8bd6741bb3fd0d9e3811dbf7681454436125ccb7afeca31c9000001400000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000065f17e71a43e0eb6a43e0eb6a43e0eb6a43e0eb6a43e0eb615a9c4f4c75d79ce22330ca2cdb6c1a6ede1f6d94ba28016eeb25e5578913ccb", - "l1ToL2MessagesHash": "0xa10cc8559615be5a44cfb608374b1f84fd11cdb5844ebffafd92a77c068350f1", - "publicInputsHash": "0x1cf2b46b77f14cccda98bf1892afb0c0dbd3bfb60aefab5c167a0dc479348993" + "header": "0x23464816bf0c3a39a98fcfba3153cdc11638cf5ac8fa184581a066f109dbadf800000002000000000000000000000000000000000000000000000000000000000000000280dc9d246537063813894f7edb41c694fbd803946807e6e3e875a6bbd91b531b2673dd78c65e0745b5000b70dcda092ae5aa3a7ab292eaa4bd01f1f4f22039a43c00faec8dc481e71433eb21f1dd016134bf403950c146783ba1928cddcb315d2fdcd19872e0cfe7bdc83a7fd73e581a0a9f8d61ffc575efb15f35d93d2138fa00000020023ef973dbaa366409f7a01a4ced696227685ce75e57b510d0e7015ebfa72c5000000200231b77b7e0311a71fae5cec0f0281816950f94a24bfc2e67c5ae8619c6ed4c88000002802ae3a1bf2752c8c8bd6741bb3fd0d9e3811dbf7681454436125ccb7afeca31c9000001400000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000065f44187a43e0eb6a43e0eb6a43e0eb6a43e0eb6a43e0eb615a9c4f4c75d79ce22330ca2cdb6c1a6ede1f6d94ba28016eeb25e5578913ccb", + "publicInputsHash": "0x30416c917f36298b46b64463261d31becd68b709b642723b92309535ac1fd21c" } } \ No newline at end of file diff --git a/l1-contracts/test/harnesses/NewInboxHarness.sol b/l1-contracts/test/harnesses/InboxHarness.sol similarity index 75% rename from l1-contracts/test/harnesses/NewInboxHarness.sol rename to l1-contracts/test/harnesses/InboxHarness.sol index 93f3b1b4f06..0a2e787021d 100644 --- a/l1-contracts/test/harnesses/NewInboxHarness.sol +++ b/l1-contracts/test/harnesses/InboxHarness.sol @@ -2,14 +2,13 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {NewInbox} from "../../src/core/messagebridge/NewInbox.sol"; +import {Inbox} from "../../src/core/messagebridge/Inbox.sol"; // Libraries import {Constants} from "../../src/core/libraries/ConstantsGen.sol"; -// TODO: rename to InboxHarness once all the pieces of the new message model are in place. -contract NewInboxHarness is NewInbox { - constructor(address _rollup, uint256 _height) NewInbox(_rollup, _height) {} +contract InboxHarness is Inbox { + constructor(address _rollup, uint256 _height) Inbox(_rollup, _height) {} function getSize() external view returns (uint256) { return SIZE; diff --git a/l1-contracts/test/portals/GasPortal.sol b/l1-contracts/test/portals/GasPortal.sol index 0e36e4522f7..5f4eb2dda54 100644 --- a/l1-contracts/test/portals/GasPortal.sol +++ b/l1-contracts/test/portals/GasPortal.sol @@ -31,68 +31,25 @@ contract GasPortal { * @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec * @param _to - The aztec address of the recipient * @param _amount - The amount to deposit - * @param _canceller - The address that can cancel the L1 to L2 message - * @param _deadline - The timestamp after which the entry can be cancelled * @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) * @return - The key of the entry in the Inbox */ - function depositToAztecPublic( - bytes32 _to, - uint256 _amount, - address _canceller, - uint32 _deadline, - bytes32 _secretHash - ) external payable returns (bytes32) { + function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) + external + returns (bytes32) + { // Preamble IInbox inbox = registry.getInbox(); DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2TokenAddress, 1); // Hash the message content to be reconstructed in the receiving contract - bytes32 contentHash = Hash.sha256ToField( - abi.encodeWithSignature("mint_public(bytes32,uint256,address)", _to, _amount, _canceller) - ); + bytes32 contentHash = + Hash.sha256ToField(abi.encodeWithSignature("mint_public(bytes32,uint256)", _to, _amount)); // Hold the tokens in the portal underlying.safeTransferFrom(msg.sender, address(this), _amount); // Send message to rollup - return inbox.sendL2Message{value: msg.value}(actor, _deadline, contentHash, _secretHash); - } - - /** - * @notice Cancel a public depositToAztec L1 to L2 message - * @dev only callable by the `canceller` of the message - * @param _to - The aztec address of the recipient in the original message - * @param _amount - The amount to deposit per the original message - * @param _deadline - The timestamp after which the entry can be cancelled - * @param _secretHash - The hash of the secret consumable message in the original message - * @param _fee - The fee paid to the sequencer - * @return The key of the entry in the Inbox - */ - function cancelL1ToAztecMessagePublic( - bytes32 _to, - uint256 _amount, - uint32 _deadline, - bytes32 _secretHash, - uint64 _fee - ) external returns (bytes32) { - IInbox inbox = registry.getInbox(); - DataStructures.L1Actor memory l1Actor = DataStructures.L1Actor(address(this), block.chainid); - DataStructures.L2Actor memory l2Actor = DataStructures.L2Actor(l2TokenAddress, 1); - DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ - sender: l1Actor, - recipient: l2Actor, - content: Hash.sha256ToField( - abi.encodeWithSignature("mint_public(bytes32,uint256,address)", _to, _amount, msg.sender) - ), - secretHash: _secretHash, - deadline: _deadline, - fee: _fee - }); - bytes32 entryKey = inbox.cancelL2Message(message, address(this)); - // release the funds to msg.sender (since the content hash) is derived by hashing the caller, - // we confirm that msg.sender is same as `_canceller` supplied when creating the message) - underlying.transfer(msg.sender, _amount); - return entryKey; + return inbox.sendL2Message(actor, contentHash, _secretHash); } } diff --git a/l1-contracts/test/portals/TokenPortal.sol b/l1-contracts/test/portals/TokenPortal.sol index 4551b27ce18..398134cd451 100644 --- a/l1-contracts/test/portals/TokenPortal.sol +++ b/l1-contracts/test/portals/TokenPortal.sol @@ -17,12 +17,12 @@ contract TokenPortal { IRegistry public registry; IERC20 public underlying; - bytes32 public l2TokenAddress; + bytes32 public l2Bridge; - function initialize(address _registry, address _underlying, bytes32 _l2TokenAddress) external { + function initialize(address _registry, address _underlying, bytes32 _l2Bridge) external { registry = IRegistry(_registry); underlying = IERC20(_underlying); - l2TokenAddress = _l2TokenAddress; + l2Bridge = _l2Bridge; } // docs:end:init @@ -31,32 +31,26 @@ contract TokenPortal { * @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec * @param _to - The aztec address of the recipient * @param _amount - The amount to deposit - * @param _canceller - The address that can cancel the L1 to L2 message - * @param _deadline - The timestamp after which the entry can be cancelled * @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) * @return The key of the entry in the Inbox */ - function depositToAztecPublic( - bytes32 _to, - uint256 _amount, - address _canceller, - uint32 _deadline, - bytes32 _secretHash - ) external payable returns (bytes32) { + function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash) + external + returns (bytes32) + { // Preamble IInbox inbox = registry.getInbox(); - DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2TokenAddress, 1); + DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, 1); // Hash the message content to be reconstructed in the receiving contract - bytes32 contentHash = Hash.sha256ToField( - abi.encodeWithSignature("mint_public(bytes32,uint256,address)", _to, _amount, _canceller) - ); + bytes32 contentHash = + Hash.sha256ToField(abi.encodeWithSignature("mint_public(bytes32,uint256)", _to, _amount)); // Hold the tokens in the portal underlying.safeTransferFrom(msg.sender, address(this), _amount); // Send message to rollup - return inbox.sendL2Message{value: msg.value}(actor, _deadline, contentHash, _secretHash); + return inbox.sendL2Message(actor, contentHash, _secretHash); } // docs:end:deposit_public @@ -65,29 +59,22 @@ contract TokenPortal { * @notice Deposit funds into the portal and adds an L2 message which can only be consumed privately on Aztec * @param _secretHashForRedeemingMintedNotes - The hash of the secret to redeem minted notes privately on Aztec. The hash should be 254 bits (so it can fit in a Field element) * @param _amount - The amount to deposit - * @param _canceller - The address that can cancel the L1 to L2 message - * @param _deadline - The timestamp after which the entry can be cancelled * @param _secretHashForL2MessageConsumption - The hash of the secret consumable L1 to L2 message. The hash should be 254 bits (so it can fit in a Field element) * @return The key of the entry in the Inbox */ function depositToAztecPrivate( bytes32 _secretHashForRedeemingMintedNotes, uint256 _amount, - address _canceller, - uint32 _deadline, bytes32 _secretHashForL2MessageConsumption - ) external payable returns (bytes32) { + ) external returns (bytes32) { // Preamble IInbox inbox = registry.getInbox(); - DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2TokenAddress, 1); + DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, 1); // Hash the message content to be reconstructed in the receiving contract bytes32 contentHash = Hash.sha256ToField( abi.encodeWithSignature( - "mint_private(bytes32,uint256,address)", - _secretHashForRedeemingMintedNotes, - _amount, - _canceller + "mint_private(bytes32,uint256)", _secretHashForRedeemingMintedNotes, _amount ) ); @@ -95,94 +82,10 @@ contract TokenPortal { underlying.safeTransferFrom(msg.sender, address(this), _amount); // Send message to rollup - return inbox.sendL2Message{value: msg.value}( - actor, _deadline, contentHash, _secretHashForL2MessageConsumption - ); + return inbox.sendL2Message(actor, contentHash, _secretHashForL2MessageConsumption); } // docs:end:deposit_private - // docs:start:token_portal_cancel - /** - * @notice Cancel a public depositToAztec L1 to L2 message - * @dev only callable by the `canceller` of the message - * @param _to - The aztec address of the recipient in the original message - * @param _amount - The amount to deposit per the original message - * @param _deadline - The timestamp after which the entry can be cancelled - * @param _secretHash - The hash of the secret consumable message in the original message - * @param _fee - The fee paid to the sequencer - * @return The key of the entry in the Inbox - */ - function cancelL1ToAztecMessagePublic( - bytes32 _to, - uint256 _amount, - uint32 _deadline, - bytes32 _secretHash, - uint64 _fee - ) external returns (bytes32) { - IInbox inbox = registry.getInbox(); - DataStructures.L1Actor memory l1Actor = DataStructures.L1Actor(address(this), block.chainid); - DataStructures.L2Actor memory l2Actor = DataStructures.L2Actor(l2TokenAddress, 1); - DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ - sender: l1Actor, - recipient: l2Actor, - content: Hash.sha256ToField( - abi.encodeWithSignature("mint_public(bytes32,uint256,address)", _to, _amount, msg.sender) - ), - secretHash: _secretHash, - deadline: _deadline, - fee: _fee - }); - bytes32 entryKey = inbox.cancelL2Message(message, address(this)); - // release the funds to msg.sender (since the content hash (& entry key) is derived by hashing the caller, - // we confirm that msg.sender is same as `_canceller` supplied when creating the message) - underlying.transfer(msg.sender, _amount); - return entryKey; - } - - /** - * @notice Cancel a private depositToAztec L1 to L2 message - * @dev only callable by the `canceller` of the message - * @param _secretHashForRedeemingMintedNotes - The hash of the secret to redeem minted notes privately on Aztec - * @param _amount - The amount to deposit per the original message - * @param _deadline - The timestamp after which the entry can be cancelled - * @param _secretHashForL2MessageConsumption - The hash of the secret consumable L1 to L2 message - * @param _fee - The fee paid to the sequencer - * @return The key of the entry in the Inbox - */ - function cancelL1ToAztecMessagePrivate( - bytes32 _secretHashForRedeemingMintedNotes, - uint256 _amount, - uint32 _deadline, - bytes32 _secretHashForL2MessageConsumption, - uint64 _fee - ) external returns (bytes32) { - IInbox inbox = registry.getInbox(); - DataStructures.L1Actor memory l1Actor = DataStructures.L1Actor(address(this), block.chainid); - DataStructures.L2Actor memory l2Actor = DataStructures.L2Actor(l2TokenAddress, 1); - DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ - sender: l1Actor, - recipient: l2Actor, - content: Hash.sha256ToField( - abi.encodeWithSignature( - "mint_private(bytes32,uint256,address)", - _secretHashForRedeemingMintedNotes, - _amount, - msg.sender - ) - ), - secretHash: _secretHashForL2MessageConsumption, - deadline: _deadline, - fee: _fee - }); - bytes32 entryKey = inbox.cancelL2Message(message, address(this)); - // release the funds to msg.sender (since the content hash (& entry key) is derived by hashing the caller, - // we confirm that msg.sender is same as `_canceller` supplied when creating the message) - underlying.transfer(msg.sender, _amount); - return entryKey; - } - - // docs:end:token_portal_cancel - // docs:start:token_portal_withdraw /** * @notice Withdraw funds from the portal @@ -198,7 +101,7 @@ contract TokenPortal { returns (bytes32) { DataStructures.L2ToL1Msg memory message = DataStructures.L2ToL1Msg({ - sender: DataStructures.L2Actor(l2TokenAddress, 1), + sender: DataStructures.L2Actor(l2Bridge, 1), recipient: DataStructures.L1Actor(address(this), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature( diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index 25457ab2c9a..9230214d09d 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; // Rollup Processor import {Rollup} from "../../src/core/Rollup.sol"; import {AvailabilityOracle} from "../../src/core/availability_oracle/AvailabilityOracle.sol"; -import {Inbox} from "../../src/core/messagebridge/Inbox.sol"; +import {Constants} from "../../src/core/libraries/ConstantsGen.sol"; import {Registry} from "../../src/core/messagebridge/Registry.sol"; import {Outbox} from "../../src/core/messagebridge/Outbox.sol"; import {DataStructures} from "../../src/core/libraries/DataStructures.sol"; @@ -21,22 +21,15 @@ import {TokenPortal} from "./TokenPortal.sol"; import {PortalERC20} from "./PortalERC20.sol"; contract TokenPortalTest is Test { - event MessageAdded( - bytes32 indexed entryKey, - address indexed sender, - bytes32 indexed recipient, - uint256 senderChainId, - uint256 recipientVersion, - uint32 deadline, - uint64 fee, - bytes32 content, - bytes32 secretHash - ); - event L1ToL2MessageCancelled(bytes32 indexed entryKey); + using Hash for DataStructures.L1ToL2Msg; + + uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_L2_BLOCK_NUM + 1; + + event LeafInserted(uint256 indexed blockNumber, uint256 index, bytes32 value); event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); Registry internal registry; - Inbox internal inbox; + IInbox internal inbox; Outbox internal outbox; Rollup internal rollup; bytes32 internal l2TokenAddress = bytes32(uint256(0x42)); @@ -55,7 +48,6 @@ contract TokenPortalTest is Test { // this hash is just a random 32 byte string bytes32 internal secretHashForRedeemingMintedNotes = 0x157e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0; - uint64 internal bid = 1 ether; // params for withdraw: address internal recipient = address(0xdead); @@ -63,9 +55,9 @@ contract TokenPortalTest is Test { function setUp() public { registry = new Registry(); - inbox = new Inbox(address(registry)); outbox = new Outbox(address(registry)); rollup = new Rollup(registry, new AvailabilityOracle()); + inbox = rollup.INBOX(); registry.upgrade(address(rollup), address(inbox), address(outbox)); @@ -87,19 +79,14 @@ contract TokenPortalTest is Test { recipient: DataStructures.L2Actor(l2TokenAddress, 1), content: Hash.sha256ToField( abi.encodeWithSignature( - "mint_private(bytes32,uint256,address)", - secretHashForRedeemingMintedNotes, - amount, - _canceller + "mint_private(bytes32,uint256)", secretHashForRedeemingMintedNotes, amount ) ), - secretHash: secretHashForL2MessageConsumption, - deadline: deadline, - fee: bid + secretHash: secretHashForL2MessageConsumption }); } - function _createExpectedMintPublicL1ToL2Message(address _canceller) + function _createExpectedMintPublicL1ToL2Message() internal view returns (DataStructures.L1ToL2Msg memory) @@ -107,12 +94,8 @@ contract TokenPortalTest is Test { return DataStructures.L1ToL2Msg({ sender: DataStructures.L1Actor(address(tokenPortal), block.chainid), recipient: DataStructures.L2Actor(l2TokenAddress, 1), - content: Hash.sha256ToField( - abi.encodeWithSignature("mint_public(bytes32,uint256,address)", to, amount, _canceller) - ), - secretHash: secretHashForL2MessageConsumption, - deadline: deadline, - fee: bid + content: Hash.sha256ToField(abi.encodeWithSignature("mint_public(bytes32,uint256)", to, amount)), + secretHash: secretHashForL2MessageConsumption }); } @@ -124,39 +107,23 @@ contract TokenPortalTest is Test { // Check for the expected message DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPrivateL1ToL2Message(address(this)); - bytes32 expectedEntryKey = inbox.computeEntryKey(expectedMessage); + + bytes32 expectedLeaf = expectedMessage.sha256ToField(); // Check the event was emitted vm.expectEmit(true, true, true, true); // event we expect - emit MessageAdded( - expectedEntryKey, - expectedMessage.sender.actor, - expectedMessage.recipient.actor, - expectedMessage.sender.chainId, - expectedMessage.recipient.version, - expectedMessage.deadline, - expectedMessage.fee, - expectedMessage.content, - expectedMessage.secretHash - ); + emit LeafInserted(FIRST_REAL_TREE_NUM, 0, expectedLeaf); + // event we will get // Perform op - bytes32 entryKey = tokenPortal.depositToAztecPrivate{value: bid}( - secretHashForRedeemingMintedNotes, - amount, - address(this), - deadline, - secretHashForL2MessageConsumption + bytes32 leaf = tokenPortal.depositToAztecPrivate( + secretHashForRedeemingMintedNotes, amount, secretHashForL2MessageConsumption ); - assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match"); + assertEq(leaf, expectedLeaf, "returned leaf and calculated leaf should match"); - // Check that the message is in the inbox - DataStructures.Entry memory entry = inbox.get(entryKey); - assertEq(entry.count, 1); - - return entryKey; + return leaf; } function testDepositPublic() public returns (bytes32) { @@ -165,131 +132,20 @@ contract TokenPortalTest is Test { portalERC20.approve(address(tokenPortal), mintAmount); // Check for the expected message - DataStructures.L1ToL2Msg memory expectedMessage = - _createExpectedMintPublicL1ToL2Message(address(this)); - bytes32 expectedEntryKey = inbox.computeEntryKey(expectedMessage); + DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPublicL1ToL2Message(); + bytes32 expectedLeaf = expectedMessage.sha256ToField(); // Check the event was emitted vm.expectEmit(true, true, true, true); // event we expect - emit MessageAdded( - expectedEntryKey, - expectedMessage.sender.actor, - expectedMessage.recipient.actor, - expectedMessage.sender.chainId, - expectedMessage.recipient.version, - expectedMessage.deadline, - expectedMessage.fee, - expectedMessage.content, - expectedMessage.secretHash - ); + emit LeafInserted(FIRST_REAL_TREE_NUM, 0, expectedLeaf); // Perform op - bytes32 entryKey = tokenPortal.depositToAztecPublic{value: bid}( - to, amount, address(this), deadline, secretHashForL2MessageConsumption - ); + bytes32 leaf = tokenPortal.depositToAztecPublic(to, amount, secretHashForL2MessageConsumption); - assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match"); + assertEq(leaf, expectedLeaf, "returned leaf and calculated leaf should match"); - // Check that the message is in the inbox - DataStructures.Entry memory entry = inbox.get(entryKey); - assertEq(entry.count, 1); - - return entryKey; - } - - function testCancelPublic() public { - bytes32 expectedEntryKey = testDepositPublic(); - // now cancel the message - move time forward (post deadline) - vm.warp(deadline + 1 days); - - // ensure no one else can cancel the message: - vm.startPrank(address(0xdead)); - bytes32 expectedWrongEntryKey = - inbox.computeEntryKey(_createExpectedMintPublicL1ToL2Message(address(0xdead))); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey) - ); - tokenPortal.cancelL1ToAztecMessagePublic( - to, amount, deadline, secretHashForL2MessageConsumption, bid - ); - vm.stopPrank(); - - // ensure cant cancel with cancelPrivate (since deposit was public) - expectedWrongEntryKey = - inbox.computeEntryKey(_createExpectedMintPrivateL1ToL2Message(address(this))); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey) - ); - tokenPortal.cancelL1ToAztecMessagePrivate( - secretHashForRedeemingMintedNotes, amount, deadline, secretHashForL2MessageConsumption, bid - ); - - // actually cancel the message - // check event was emitted - vm.expectEmit(true, false, false, false); - // expected event: - emit L1ToL2MessageCancelled(expectedEntryKey); - // perform op - bytes32 entryKey = tokenPortal.cancelL1ToAztecMessagePublic( - to, amount, deadline, secretHashForL2MessageConsumption, bid - ); - - assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match"); - assertFalse(inbox.contains(entryKey), "entry still in inbox"); - assertEq( - portalERC20.balanceOf(address(this)), - mintAmount, - "assets should be transferred back to this contract" - ); - assertEq(portalERC20.balanceOf(address(tokenPortal)), 0, "portal should have no assets"); - } - - function testCancelPrivate() public { - bytes32 expectedEntryKey = testDepositPrivate(); - // now cancel the message - move time forward (post deadline) - vm.warp(deadline + 1 days); - - // ensure no one else can cancel the message: - vm.startPrank(address(0xdead)); - bytes32 expectedWrongEntryKey = - inbox.computeEntryKey(_createExpectedMintPrivateL1ToL2Message(address(0xdead))); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey) - ); - tokenPortal.cancelL1ToAztecMessagePrivate( - secretHashForRedeemingMintedNotes, amount, deadline, secretHashForL2MessageConsumption, bid - ); - vm.stopPrank(); - - // ensure cant cancel with cancelPublic (since deposit was private) - expectedWrongEntryKey = - inbox.computeEntryKey(_createExpectedMintPublicL1ToL2Message(address(this))); - vm.expectRevert( - abi.encodeWithSelector(Errors.Inbox__NothingToConsume.selector, expectedWrongEntryKey) - ); - tokenPortal.cancelL1ToAztecMessagePublic( - to, amount, deadline, secretHashForL2MessageConsumption, bid - ); - - // actually cancel the message - // check event was emitted - vm.expectEmit(true, false, false, false); - // expected event: - emit L1ToL2MessageCancelled(expectedEntryKey); - // perform op - bytes32 entryKey = tokenPortal.cancelL1ToAztecMessagePrivate( - secretHashForRedeemingMintedNotes, amount, deadline, secretHashForL2MessageConsumption, bid - ); - - assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match"); - assertFalse(inbox.contains(entryKey), "entry still in inbox"); - assertEq( - portalERC20.balanceOf(address(this)), - mintAmount, - "assets should be transferred back to this contract" - ); - assertEq(portalERC20.balanceOf(address(tokenPortal)), 0, "portal should have no assets"); + return leaf; } function _createWithdrawMessageForOutbox(address _designatedCaller) diff --git a/l1-contracts/test/portals/UniswapPortal.sol b/l1-contracts/test/portals/UniswapPortal.sol index 8fce7fbed67..5a76055e451 100644 --- a/l1-contracts/test/portals/UniswapPortal.sol +++ b/l1-contracts/test/portals/UniswapPortal.sol @@ -50,8 +50,6 @@ contract UniswapPortal { * @param _amountOutMinimum - The minimum amount of output assets to receive from the swap (slippage protection) * @param _aztecRecipient - The aztec address to receive the output assets * @param _secretHashForL1ToL2Message - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) - * @param _deadlineForL1ToL2Message - deadline for when the L1 to L2 message (to mint output assets in L2) must be consumed by - * @param _canceller - The ethereum address that can cancel the deposit * @param _withCaller - When true, using `msg.sender` as the caller, otherwise address(0) * @return The entryKey of the deposit transaction in the Inbox */ @@ -63,10 +61,8 @@ contract UniswapPortal { uint256 _amountOutMinimum, bytes32 _aztecRecipient, bytes32 _secretHashForL1ToL2Message, - uint32 _deadlineForL1ToL2Message, - address _canceller, bool _withCaller - ) public payable returns (bytes32) { + ) public returns (bytes32) { LocalSwapVars memory vars; vars.inputAsset = TokenPortal(_inputTokenPortal).underlying(); @@ -78,7 +74,7 @@ contract UniswapPortal { // prevent stack too deep errors vars.contentHash = Hash.sha256ToField( abi.encodeWithSignature( - "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,uint32,address,address)", + "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)", _inputTokenPortal, _inAmount, _uniswapFeeTier, @@ -86,8 +82,6 @@ contract UniswapPortal { _amountOutMinimum, _aztecRecipient, _secretHashForL1ToL2Message, - _deadlineForL1ToL2Message, - _canceller, _withCaller ? msg.sender : address(0) ) ); @@ -125,8 +119,8 @@ contract UniswapPortal { vars.outputAsset.approve(address(_outputTokenPortal), amountOut); // Deposit the output asset to the L2 via its portal - return TokenPortal(_outputTokenPortal).depositToAztecPublic{value: msg.value}( - _aztecRecipient, amountOut, _canceller, _deadlineForL1ToL2Message, _secretHashForL1ToL2Message + return TokenPortal(_outputTokenPortal).depositToAztecPublic( + _aztecRecipient, amountOut, _secretHashForL1ToL2Message ); } // docs:end:solidity_uniswap_swap_public @@ -144,8 +138,6 @@ contract UniswapPortal { * @param _amountOutMinimum - The minimum amount of output assets to receive from the swap (slippage protection) * @param _secretHashForRedeemingMintedNotes - The hash of the secret to redeem minted notes privately on Aztec. The hash should be 254 bits (so it can fit in a Field element) * @param _secretHashForL1ToL2Message - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element) - * @param _deadlineForL1ToL2Message - deadline for when the L1 to L2 message (to mint output assets in L2) must be consumed by - * @param _canceller - The ethereum address that can cancel the deposit * @param _withCaller - When true, using `msg.sender` as the caller, otherwise address(0) * @return The entryKey of the deposit transaction in the Inbox */ @@ -157,10 +149,8 @@ contract UniswapPortal { uint256 _amountOutMinimum, bytes32 _secretHashForRedeemingMintedNotes, bytes32 _secretHashForL1ToL2Message, - uint32 _deadlineForL1ToL2Message, - address _canceller, bool _withCaller - ) public payable returns (bytes32) { + ) public returns (bytes32) { LocalSwapVars memory vars; vars.inputAsset = TokenPortal(_inputTokenPortal).underlying(); @@ -172,7 +162,7 @@ contract UniswapPortal { // prevent stack too deep errors vars.contentHash = Hash.sha256ToField( abi.encodeWithSignature( - "swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,uint32,address,address)", + "swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,address)", _inputTokenPortal, _inAmount, _uniswapFeeTier, @@ -180,8 +170,6 @@ contract UniswapPortal { _amountOutMinimum, _secretHashForRedeemingMintedNotes, _secretHashForL1ToL2Message, - _deadlineForL1ToL2Message, - _canceller, _withCaller ? msg.sender : address(0) ) ); @@ -219,12 +207,8 @@ contract UniswapPortal { vars.outputAsset.approve(address(_outputTokenPortal), amountOut); // Deposit the output asset to the L2 via its portal - return TokenPortal(_outputTokenPortal).depositToAztecPrivate{value: msg.value}( - _secretHashForRedeemingMintedNotes, - amountOut, - _canceller, - _deadlineForL1ToL2Message, - _secretHashForL1ToL2Message + return TokenPortal(_outputTokenPortal).depositToAztecPrivate( + _secretHashForRedeemingMintedNotes, amountOut, _secretHashForL1ToL2Message ); } } diff --git a/l1-contracts/test/portals/UniswapPortal.t.sol b/l1-contracts/test/portals/UniswapPortal.t.sol index 45792a2f1bc..caf48bf6137 100644 --- a/l1-contracts/test/portals/UniswapPortal.t.sol +++ b/l1-contracts/test/portals/UniswapPortal.t.sol @@ -5,7 +5,6 @@ import "forge-std/Test.sol"; // Rollup Processor import {Rollup} from "../../src/core/Rollup.sol"; import {AvailabilityOracle} from "../../src/core/availability_oracle/AvailabilityOracle.sol"; -import {Inbox} from "../../src/core/messagebridge/Inbox.sol"; import {Registry} from "../../src/core/messagebridge/Registry.sol"; import {Outbox} from "../../src/core/messagebridge/Outbox.sol"; import {DataStructures} from "../../src/core/libraries/DataStructures.sol"; @@ -25,7 +24,6 @@ contract UniswapPortalTest is Test { IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); IERC20 public constant WETH9 = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - Inbox internal inbox; Outbox internal outbox; Rollup internal rollup; bytes32 internal l2TokenAddress = bytes32(uint256(0x1)); @@ -39,7 +37,6 @@ contract UniswapPortalTest is Test { bytes32 internal secretHash = bytes32(0); uint24 internal uniswapFeePool = 3000; // 0.3% fee uint256 internal amountOutMinimum = 0; - uint32 internal deadlineForL1ToL2Message; // set after fork is activated bytes32 internal aztecRecipient = bytes32(uint256(0x3)); bytes32 internal secretHashForRedeemingMintedNotes = bytes32(uint256(0x4)); @@ -47,13 +44,11 @@ contract UniswapPortalTest is Test { // fork mainnet uint256 forkId = vm.createFork(vm.rpcUrl("mainnet_fork")); vm.selectFork(forkId); - deadlineForL1ToL2Message = uint32(block.timestamp + 1 days); Registry registry = new Registry(); - inbox = new Inbox(address(registry)); outbox = new Outbox(address(registry)); rollup = new Rollup(registry, new AvailabilityOracle()); - registry.upgrade(address(rollup), address(inbox), address(outbox)); + registry.upgrade(address(rollup), address(rollup.INBOX()), address(outbox)); daiTokenPortal = new TokenPortal(); daiTokenPortal.initialize(address(registry), address(DAI), l2TokenAddress); @@ -105,7 +100,7 @@ contract UniswapPortalTest is Test { recipient: DataStructures.L1Actor(address(uniswapPortal), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature( - "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,uint32,address,address)", + "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)", address(daiTokenPortal), amount, uniswapFeePool, @@ -113,8 +108,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, _aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), _caller ) ) @@ -137,7 +130,7 @@ contract UniswapPortalTest is Test { recipient: DataStructures.L1Actor(address(uniswapPortal), block.chainid), content: Hash.sha256ToField( abi.encodeWithSignature( - "swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,uint32,address,address)", + "swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,address)", address(daiTokenPortal), amount, uniswapFeePool, @@ -145,8 +138,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, _secretHashForRedeemingMintedNotes, secretHash, - deadlineForL1ToL2Message, - address(this), _caller ) ) @@ -181,8 +172,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), true ); } @@ -208,8 +197,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), true ); } @@ -234,8 +221,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, newAztecRecipient, // change recipient of swapped token to some other address secretHash, - deadlineForL1ToL2Message, - address(this), true ); } @@ -254,8 +239,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), true ); @@ -263,8 +246,6 @@ contract UniswapPortalTest is Test { assertEq(DAI.balanceOf(address(daiTokenPortal)), 0); // there should be some weth in the weth portal assertGt(WETH9.balanceOf(address(wethTokenPortal)), 0); - // there should be a message in the inbox: - assertEq(inbox.get(l1ToL2EntryKey).count, 1); // there should be no message in the outbox: assertFalse(outbox.contains(daiWithdrawEntryKey)); assertFalse(outbox.contains(swapEntryKey)); @@ -287,8 +268,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), false ); // check that swap happened: @@ -296,8 +275,6 @@ contract UniswapPortalTest is Test { assertEq(DAI.balanceOf(address(daiTokenPortal)), 0); // there should be some weth in the weth portal assertGt(WETH9.balanceOf(address(wethTokenPortal)), 0); - // there should be a message in the inbox: - assertEq(inbox.get(l1ToL2EntryKey).count, 1); // there should be no message in the outbox: assertFalse(outbox.contains(daiWithdrawEntryKey)); assertFalse(outbox.contains(swapEntryKey)); @@ -323,8 +300,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), true ); @@ -340,58 +315,11 @@ contract UniswapPortalTest is Test { amountOutMinimum, aztecRecipient, secretHash, - deadlineForL1ToL2Message, - address(this), false ); vm.stopPrank(); } - // after the portal does the swap, it adds a L1 to L2 message to the inbox. - // to mint `outputToken` to the `aztecRecipient` on L2. This test checks that - // if the sequencer doesn't consume the L1->L2 message, then `canceller` can - // cancel the message and retrieve the funds (instead of them being stuck on the portal) - function testMessageToInboxIsCancellable() public { - bytes32 daiWithdrawEntryKey = - _createDaiWithdrawMessage(address(uniswapPortal), address(uniswapPortal)); - bytes32 swapEntryKey = _createUniswapSwapMessagePublic(aztecRecipient, address(this)); - _addMessagesToOutbox(daiWithdrawEntryKey, swapEntryKey); - - bytes32 l1ToL2EntryKey = uniswapPortal.swapPublic{value: 1 ether}( - address(daiTokenPortal), - amount, - uniswapFeePool, - address(wethTokenPortal), - amountOutMinimum, - aztecRecipient, - secretHash, - deadlineForL1ToL2Message, - address(this), // this address should be able to cancel - true - ); - - uint256 wethAmountOut = WETH9.balanceOf(address(wethTokenPortal)); - // cancel L1 to L2Message - first move ahead of deadline - vm.warp(deadlineForL1ToL2Message + 1 days); - // check event was emitted - vm.expectEmit(true, false, false, false); - // expected event: - emit L1ToL2MessageCancelled(l1ToL2EntryKey); - // perform op - // TODO(2167) - Update UniswapPortal properly with new portal standard. - bytes32 entryKey = wethTokenPortal.cancelL1ToAztecMessagePublic( - aztecRecipient, wethAmountOut, deadlineForL1ToL2Message, secretHash, 1 ether - ); - assertEq(entryKey, l1ToL2EntryKey, "returned entry key and calculated entryKey should match"); - assertFalse(inbox.contains(entryKey), "entry still in inbox"); - assertEq( - WETH9.balanceOf(address(this)), - wethAmountOut, - "assets should be transferred back to this contract" - ); - assertEq(WETH9.balanceOf(address(wethTokenPortal)), 0, "portal should have no assets"); - } - function testRevertIfSwapMessageWasForDifferentPublicOrPrivateFlow() public { bytes32 daiWithdrawEntryKey = _createDaiWithdrawMessage(address(uniswapPortal), address(uniswapPortal)); @@ -414,8 +342,6 @@ contract UniswapPortalTest is Test { amountOutMinimum, secretHashForRedeemingMintedNotes, secretHash, - deadlineForL1ToL2Message, - address(this), true ); } diff --git a/noir-projects/aztec-nr/aztec/src/messaging/l1_to_l2_message.nr b/noir-projects/aztec-nr/aztec/src/messaging/l1_to_l2_message.nr index 1c6c85167e6..102989a456a 100644 --- a/noir-projects/aztec-nr/aztec/src/messaging/l1_to_l2_message.nr +++ b/noir-projects/aztec-nr/aztec/src/messaging/l1_to_l2_message.nr @@ -4,8 +4,6 @@ use dep::protocol_types::{ hash::{pedersen_hash, sha256_to_field} }; -// TODO(#4833) remove `deadline` and `fee` from the message -// currently hardcoded to max_value and 0 respectively. struct L1ToL2Message { sender: EthAddress, chain_id: Field, @@ -14,8 +12,6 @@ struct L1ToL2Message { content: Field, secret: Field, secret_hash: Field, - deadline: u32, - fee: u64, tree_index: Field } @@ -37,8 +33,6 @@ impl L1ToL2Message { content, secret, secret_hash, - deadline: 4294967295, - fee: 0, tree_index: 0 } } @@ -56,22 +50,18 @@ impl L1ToL2Message { content: fields[4], secret, secret_hash: fields[5], - deadline: fields[6] as u32, - fee: fields[7] as u64, tree_index } } fn hash(self: Self) -> Field { - let mut hash_bytes: [u8; 256] = [0; 256]; + let mut hash_bytes = [0 as u8; 192]; let sender_bytes = self.sender.to_field().to_be_bytes(32); let chain_id_bytes = self.chain_id.to_be_bytes(32); let recipient_bytes = self.recipient.to_field().to_be_bytes(32); let version_bytes = self.version.to_be_bytes(32); let content_bytes = self.content.to_be_bytes(32); let secret_hash_bytes = self.secret_hash.to_be_bytes(32); - let deadline_bytes = (self.deadline as Field).to_be_bytes(32); - let fee_bytes = (self.fee as Field).to_be_bytes(32); for i in 0..32 { hash_bytes[i] = sender_bytes[i]; @@ -80,8 +70,6 @@ impl L1ToL2Message { hash_bytes[i + 96] = version_bytes[i]; hash_bytes[i + 128] = content_bytes[i]; hash_bytes[i + 160] = secret_hash_bytes[i]; - hash_bytes[i + 192] = deadline_bytes[i]; - hash_bytes[i + 224] = fee_bytes[i]; } let message_hash = sha256_to_field(hash_bytes); diff --git a/noir-projects/noir-contracts/contracts/gas_token_contract/src/lib.nr b/noir-projects/noir-contracts/contracts/gas_token_contract/src/lib.nr index 5210894d79f..9e9786bd2ed 100644 --- a/noir-projects/noir-contracts/contracts/gas_token_contract/src/lib.nr +++ b/noir-projects/noir-contracts/contracts/gas_token_contract/src/lib.nr @@ -6,23 +6,21 @@ pub fn calculate_fee(_context: PublicContext) -> U128 { U128::from_integer(1) } -pub fn get_bridge_gas_msg_hash(owner: AztecAddress, amount: Field, canceller: EthAddress) -> Field { - let mut hash_bytes: [u8; 100] = [0; 100]; +pub fn get_bridge_gas_msg_hash(owner: AztecAddress, amount: Field) -> Field { + let mut hash_bytes = [0; 68]; let recipient_bytes = owner.to_field().to_be_bytes(32); let amount_bytes = amount.to_be_bytes(32); - let canceller_bytes = canceller.to_field().to_be_bytes(32); for i in 0..32 { hash_bytes[i + 4] = recipient_bytes[i]; hash_bytes[i + 36] = amount_bytes[i]; - hash_bytes[i + 68] = canceller_bytes[i]; } - // Function selector: 0xefc2aae6 keccak256('mint_public(bytes32,uint256,address)') - hash_bytes[0] = 0xef; - hash_bytes[1] = 0xc2; - hash_bytes[2] = 0xaa; - hash_bytes[3] = 0xe6; + // Function selector: 0x3e87b9be keccak256('mint_public(bytes32,uint256)') + hash_bytes[0] = 0x3e; + hash_bytes[1] = 0x87; + hash_bytes[2] = 0xb9; + hash_bytes[3] = 0xbe; let content_hash = sha256_to_field(hash_bytes); content_hash diff --git a/noir-projects/noir-contracts/contracts/gas_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/gas_token_contract/src/main.nr index a6348ae618e..84af6c6af01 100644 --- a/noir-projects/noir-contracts/contracts/gas_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/gas_token_contract/src/main.nr @@ -11,8 +11,8 @@ contract GasToken { } #[aztec(public)] - fn claim_public(to: AztecAddress, amount: Field, canceller: EthAddress, secret: Field) { - let content_hash = get_bridge_gas_msg_hash(to, amount, canceller); + fn claim_public(to: AztecAddress, amount: Field, secret: Field) { + let content_hash = get_bridge_gas_msg_hash(to, amount); // Consume message and emit nullifier context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address()); diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 79b14924663..22e3162d2e3 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -260,10 +260,9 @@ contract Test { fn consume_mint_public_message( to: AztecAddress, amount: Field, - canceller: EthAddress, secret: Field ) { - let content_hash = get_mint_public_content_hash(to, amount, canceller); + let content_hash = get_mint_public_content_hash(to, amount); // Consume message and emit nullifier context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address()); } @@ -272,11 +271,10 @@ contract Test { fn consume_mint_private_message( secret_hash_for_redeeming_minted_notes: Field, amount: Field, - canceller: EthAddress, secret_for_L1_to_L2_message_consumption: Field ) { // Consume L1 to L2 message and emit nullifier - let content_hash = get_mint_private_content_hash(secret_hash_for_redeeming_minted_notes, amount, canceller); + let content_hash = get_mint_private_content_hash(secret_hash_for_redeeming_minted_notes, amount); context.consume_l1_to_l2_message( content_hash, secret_for_L1_to_L2_message_consumption, diff --git a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr index 44ced870387..9611aa25d6f 100644 --- a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr @@ -34,8 +34,8 @@ contract TokenBridge { // docs:start:claim_public // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly #[aztec(public)] - fn claim_public(to: AztecAddress, amount: Field, canceller: EthAddress, secret: Field) { - let content_hash = get_mint_public_content_hash(to, amount, canceller); + fn claim_public(to: AztecAddress, amount: Field, secret: Field) { + let content_hash = get_mint_public_content_hash(to, amount); // Consume message and emit nullifier context.consume_l1_to_l2_message(content_hash, secret, context.this_portal_address()); @@ -70,11 +70,10 @@ contract TokenBridge { fn claim_private( secret_hash_for_redeeming_minted_notes: Field, // secret hash used to redeem minted notes at a later time. This enables anyone to call this function and mint tokens to a user on their behalf amount: Field, - canceller: EthAddress, secret_for_L1_to_L2_message_consumption: Field // secret used to consume the L1 to L2 message ) { // Consume L1 to L2 message and emit nullifier - let content_hash = get_mint_private_content_hash(secret_hash_for_redeeming_minted_notes, amount, canceller); + let content_hash = get_mint_private_content_hash(secret_hash_for_redeeming_minted_notes, amount); context.consume_l1_to_l2_message( content_hash, secret_for_L1_to_L2_message_consumption, diff --git a/noir-projects/noir-contracts/contracts/token_portal_content_hash_lib/src/lib.nr b/noir-projects/noir-contracts/contracts/token_portal_content_hash_lib/src/lib.nr index f0bda067f1f..399baa4001e 100644 --- a/noir-projects/noir-contracts/contracts/token_portal_content_hash_lib/src/lib.nr +++ b/noir-projects/noir-contracts/contracts/token_portal_content_hash_lib/src/lib.nr @@ -4,23 +4,21 @@ use dep::aztec::protocol_types::hash::sha256_to_field; // Computes a content hash of a deposit/mint_public message. // Refer TokenPortal.sol for reference on L1. -pub fn get_mint_public_content_hash(owner: AztecAddress, amount: Field, canceller: EthAddress) -> Field { - let mut hash_bytes: [u8; 100] = [0; 100]; +pub fn get_mint_public_content_hash(owner: AztecAddress, amount: Field) -> Field { + let mut hash_bytes = [0; 68]; let recipient_bytes = owner.to_field().to_be_bytes(32); let amount_bytes = amount.to_be_bytes(32); - let canceller_bytes = canceller.to_field().to_be_bytes(32); for i in 0..32 { hash_bytes[i + 4] = recipient_bytes[i]; hash_bytes[i + 36] = amount_bytes[i]; - hash_bytes[i + 68] = canceller_bytes[i]; } - // Function selector: 0xefc2aae6 keccak256('mint_public(bytes32,uint256,address)') - hash_bytes[0] = 0xef; - hash_bytes[1] = 0xc2; - hash_bytes[2] = 0xaa; - hash_bytes[3] = 0xe6; + // Function selector: 0x3e87b9be keccak256('mint_public(bytes32,uint256)') + hash_bytes[0] = 0x3e; + hash_bytes[1] = 0x87; + hash_bytes[2] = 0xb9; + hash_bytes[3] = 0xbe; let content_hash = sha256_to_field(hash_bytes); content_hash @@ -32,25 +30,22 @@ pub fn get_mint_public_content_hash(owner: AztecAddress, amount: Field, cancelle // Refer TokenPortal.sol for reference on L1. pub fn get_mint_private_content_hash( secret_hash_for_redeeming_minted_notes: Field, - amount: Field, - canceller: EthAddress + amount: Field ) -> Field { - let mut hash_bytes: [u8; 100] = [0; 100]; + let mut hash_bytes = [0; 68]; let secret_hash_bytes = secret_hash_for_redeeming_minted_notes.to_be_bytes(32); let amount_bytes = amount.to_be_bytes(32); - let canceller_bytes = canceller.to_field().to_be_bytes(32); for i in 0..32 { hash_bytes[i + 4] = secret_hash_bytes[i]; hash_bytes[i + 36] = amount_bytes[i]; - hash_bytes[i + 68] = canceller_bytes[i]; } - // Function selector: 0xf512262e keccak256('mint_private(bytes32,uint256,address)') - hash_bytes[0] = 0xf5; - hash_bytes[1] = 0x12; - hash_bytes[2] = 0x26; - hash_bytes[3] = 0x2e; + // Function selector: 0xefa012c1 keccak256('mint_private(bytes32,uint256)') + hash_bytes[0] = 0xef; + hash_bytes[1] = 0xa0; + hash_bytes[2] = 0x12; + hash_bytes[3] = 0xc1; let content_hash = sha256_to_field(hash_bytes); content_hash diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr index 78c4386dc82..c092d6bf081 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr @@ -41,8 +41,6 @@ contract Uniswap { // params for the depositing output_asset back to Aztec recipient: AztecAddress, secret_hash_for_L1_to_l2_message: Field, - deadline_for_L1_to_l2_message: Field, - canceller_for_L1_to_L2_message: EthAddress, caller_on_L1: EthAddress, // nonce for someone to call swap on sender's behalf nonce_for_swap_approval: Field @@ -89,8 +87,6 @@ contract Uniswap { minimum_output_amount, recipient, secret_hash_for_L1_to_l2_message, - deadline_for_L1_to_l2_message, - canceller_for_L1_to_L2_message, caller_on_L1 ); context.message_portal(context.this_portal_address(), content_hash); @@ -112,8 +108,6 @@ contract Uniswap { // params for the depositing output_asset back to Aztec secret_hash_for_redeeming_minted_notes: Field,// secret hash used to redeem minted notes at a later time. This enables anyone to call this function and mint tokens to a user on their behalf secret_hash_for_L1_to_l2_message: Field, // for when l1 uniswap portal inserts the message to consume output assets on L2 - deadline_for_L1_to_l2_message: Field, // for when l1 uniswap portal inserts the message to consume output assets on L2 - canceller_for_L1_to_L2_message: EthAddress, // L1 address of who can cancel the message to consume assets on L2. caller_on_L1: EthAddress // ethereum address that can call this function on the L1 portal (0x0 if anyone can call) ) { // Assert that user provided token address is same as expected by token bridge. @@ -160,8 +154,6 @@ contract Uniswap { minimum_output_amount, secret_hash_for_redeeming_minted_notes, secret_hash_for_L1_to_l2_message, - deadline_for_L1_to_l2_message, - canceller_for_L1_to_L2_message, caller_on_L1 ); context.message_portal(context.this_portal_address(), content_hash); diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/util.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/util.nr index b5928337197..a2f08dea4cf 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/util.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/util.nr @@ -12,11 +12,9 @@ pub fn compute_swap_public_content_hash( minimum_output_amount: Field, aztec_recipient: AztecAddress, secret_hash_for_L1_to_l2_message: Field, - deadline_for_L1_to_l2_message: Field, - canceller_for_L1_to_L2_message: EthAddress, caller_on_L1: EthAddress ) -> Field { - let mut hash_bytes: [u8; 324] = [0; 324]; // 10 fields of 32 bytes each + 4 bytes fn selector + let mut hash_bytes = [0; 260]; // 8 fields of 32 bytes each + 4 bytes fn selector let input_token_portal_bytes = input_asset_bridge_portal_address.to_field().to_be_bytes(32); let in_amount_bytes = input_amount.to_be_bytes(32); @@ -25,15 +23,13 @@ pub fn compute_swap_public_content_hash( let amount_out_min_bytes = minimum_output_amount.to_be_bytes(32); let aztec_recipient_bytes = aztec_recipient.to_field().to_be_bytes(32); let secret_hash_for_L1_to_l2_message_bytes = secret_hash_for_L1_to_l2_message.to_be_bytes(32); - let deadline_for_L1_to_l2_message_bytes = deadline_for_L1_to_l2_message.to_be_bytes(32); - let canceller_bytes = canceller_for_L1_to_L2_message.to_field().to_be_bytes(32); let caller_on_L1_bytes = caller_on_L1.to_field().to_be_bytes(32); - // function selector: 0xf3068cac keccak256("swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,uint32,address,address)") - hash_bytes[0] = 0xf3; - hash_bytes[1] = 0x06; - hash_bytes[2] = 0x8c; - hash_bytes[3] = 0xac; + // function selector: 0xf18186d8 keccak256("swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)") + hash_bytes[0] = 0xf1; + hash_bytes[1] = 0x81; + hash_bytes[2] = 0x86; + hash_bytes[3] = 0xd8; for i in 0..32 { hash_bytes[i + 4] = input_token_portal_bytes[i]; @@ -43,9 +39,7 @@ pub fn compute_swap_public_content_hash( hash_bytes[i + 132] = amount_out_min_bytes[i]; hash_bytes[i + 164] = aztec_recipient_bytes[i]; hash_bytes[i + 196] = secret_hash_for_L1_to_l2_message_bytes[i]; - hash_bytes[i + 228] = deadline_for_L1_to_l2_message_bytes[i]; - hash_bytes[i + 260] = canceller_bytes[i]; - hash_bytes[i + 292] = caller_on_L1_bytes[i]; + hash_bytes[i + 228] = caller_on_L1_bytes[i]; } let content_hash = sha256_to_field(hash_bytes); @@ -64,11 +58,9 @@ pub fn compute_swap_private_content_hash( minimum_output_amount: Field, secret_hash_for_redeeming_minted_notes: Field, secret_hash_for_L1_to_l2_message: Field, - deadline_for_L1_to_l2_message: Field, - canceller_for_L1_to_L2_message: EthAddress, caller_on_L1: EthAddress ) -> Field { - let mut hash_bytes: [u8; 324] = [0; 324]; // 10 fields of 32 bytes each + 4 bytes fn selector + let mut hash_bytes = [0; 260]; // 8 fields of 32 bytes each + 4 bytes fn selector let input_token_portal_bytes = input_asset_bridge_portal_address.to_field().to_be_bytes(32); let in_amount_bytes = input_amount.to_be_bytes(32); @@ -77,15 +69,13 @@ pub fn compute_swap_private_content_hash( let amount_out_min_bytes = minimum_output_amount.to_be_bytes(32); let secret_hash_for_redeeming_minted_notes_bytes = secret_hash_for_redeeming_minted_notes.to_be_bytes(32); let secret_hash_for_L1_to_l2_message_bytes = secret_hash_for_L1_to_l2_message.to_be_bytes(32); - let deadline_for_L1_to_l2_message_bytes = deadline_for_L1_to_l2_message.to_be_bytes(32); - let canceller_bytes = canceller_for_L1_to_L2_message.to_field().to_be_bytes(32); let caller_on_L1_bytes = caller_on_L1.to_field().to_be_bytes(32); - // function selector: 0xbd87d14b keccak256("swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,uint32,address,address)") - hash_bytes[0] = 0xbd; - hash_bytes[1] = 0x87; - hash_bytes[2] = 0xd1; - hash_bytes[3] = 0x4b; + // function selector: 0x16f416eb keccak256("swap_private(address,uint256,uint24,address,uint256,bytes32,bytes32,address)") + hash_bytes[0] = 0x16; + hash_bytes[1] = 0xf4; + hash_bytes[2] = 0x16; + hash_bytes[3] = 0xeb; for i in 0..32 { hash_bytes[i + 4] = input_token_portal_bytes[i]; @@ -95,9 +85,7 @@ pub fn compute_swap_private_content_hash( hash_bytes[i + 132] = amount_out_min_bytes[i]; hash_bytes[i + 164] = secret_hash_for_redeeming_minted_notes_bytes[i]; hash_bytes[i + 196] = secret_hash_for_L1_to_l2_message_bytes[i]; - hash_bytes[i + 228] = deadline_for_L1_to_l2_message_bytes[i]; - hash_bytes[i + 260] = canceller_bytes[i]; - hash_bytes[i + 292] = caller_on_L1_bytes[i]; + hash_bytes[i + 228] = caller_on_L1_bytes[i]; } let content_hash = sha256_to_field(hash_bytes); content_hash diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr index e9809b19c81..229b8e34073 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_inputs.nr @@ -44,18 +44,13 @@ impl RootRollupInputs { components::assert_equal_constants(left, right); components::assert_prev_rollups_follow_on_from_each_other(left, right); - // Check correct l1 to l2 tree given - // Compute subtree inserting l1 to l2 messages - let l1_to_l2_subtree_root = calculate_subtree_root(self.new_l1_to_l2_messages); - // Insert subtree into the l1 to l2 data tree - // TODO(#4492): insert the root from l1_to_l2_roots here instead of the one from old inbox let empty_l1_to_l2_subtree_root = calculate_empty_tree_root(L1_TO_L2_MSG_SUBTREE_HEIGHT); let new_l1_to_l2_message_tree_snapshot = append_only_tree::insert_subtree_to_snapshot_tree( self.start_l1_to_l2_message_tree_snapshot, self.new_l1_to_l2_message_tree_root_sibling_path, empty_l1_to_l2_subtree_root, - l1_to_l2_subtree_root, + self.l1_to_l2_roots.public_inputs.converted_root, // TODO(Kev): For now we can add a test that this fits inside of // a u8. L1_TO_L2_MSG_SUBTREE_HEIGHT as u8 @@ -93,7 +88,7 @@ impl RootRollupInputs { aggregation_object, archive, header, - // TODO(#4492): update this when implementing the new message model + // TODO(#5264): update this when implementing the new message model l1_to_l2_messages_hash: compute_messages_hash(self.new_l1_to_l2_messages) } } diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_public_inputs.nr index be0fea6290f..c38a0151d1c 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_public_inputs.nr @@ -14,6 +14,6 @@ struct RootRollupPublicInputs { // New block header header: Header, - // TODO(#4492): Nuke this once message hashing is moved out + // TODO(#5264): Nuke this once message hashing is moved out l1_to_l2_messages_hash : [Field; NUM_FIELDS_PER_SHA256], } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 5e5c215925f..1209bd5d764 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -156,7 +156,7 @@ global FUNCTION_DATA_LENGTH: u64 = 2; global FUNCTION_LEAF_PREIMAGE_LENGTH: u64 = 5; global GLOBAL_VARIABLES_LENGTH: u64 = 6; global HEADER_LENGTH: u64 = 23; // 2 for last_archive, 7 for content commitment, 8 for state reference, 6 for global vars -global L1_TO_L2_MESSAGE_LENGTH: u64 = 8; +global L1_TO_L2_MESSAGE_LENGTH: u64 = 6; global L2_TO_L1_MESSAGE_LENGTH: u64 = 2; global NULLIFIER_KEY_VALIDATION_REQUEST_LENGTH = 4; global NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5; diff --git a/yarn-project/archiver/src/archiver/archiver.test.ts b/yarn-project/archiver/src/archiver/archiver.test.ts index a7a89c78bf5..c312e0471bd 100644 --- a/yarn-project/archiver/src/archiver/archiver.test.ts +++ b/yarn-project/archiver/src/archiver/archiver.test.ts @@ -1,9 +1,8 @@ import { Body, L2Block, L2BlockL2Logs, LogType } from '@aztec/circuit-types'; -import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { sleep } from '@aztec/foundation/sleep'; -import { AvailabilityOracleAbi, InboxAbi, NewInboxAbi, RollupAbi } from '@aztec/l1-artifacts'; +import { AvailabilityOracleAbi, InboxAbi, RollupAbi } from '@aztec/l1-artifacts'; import { MockProxy, mock } from 'jest-mock-extended'; import { Chain, HttpTransport, Log, PublicClient, Transaction, encodeFunctionData, toHex } from 'viem'; @@ -15,8 +14,6 @@ import { MemoryArchiverStore } from './memory_archiver_store/memory_archiver_sto describe('Archiver', () => { const rollupAddress = EthAddress.ZERO; const inboxAddress = EthAddress.ZERO; - // TODO(#4492): Nuke this once the old inbox is purged - const newInboxAddress = EthAddress.ZERO; const registryAddress = EthAddress.ZERO; const availabilityOracleAddress = EthAddress.ZERO; const blockNumbers = [1, 2, 3]; @@ -34,7 +31,6 @@ describe('Archiver', () => { rollupAddress, availabilityOracleAddress, inboxAddress, - newInboxAddress, registryAddress, archiverStore, 1000, @@ -47,47 +43,12 @@ describe('Archiver', () => { const publishTxs = blocks.map(block => block.body).map(makePublishTx); const rollupTxs = blocks.map(makeRollupTx); - // `L2Block.random(x)` creates some l1 to l2 messages. We add those, - // since it is expected by the test that these would be consumed. - // Archiver removes such messages from pending store. - // Also create some more messages to cancel and some that will stay pending. - - const messageToCancel1 = Fr.random().toString(); - const messageToCancel2 = Fr.random().toString(); - const l1ToL2MessagesToCancel = [messageToCancel1, messageToCancel2]; - const messageToStayPending1 = Fr.random().toString(); - const messageToStayPending2 = Fr.random().toString(); - - const l1ToL2MessageAddedEvents = [ - makeL1ToL2MessageAddedEvents( - 100n, - blocks[0].body.l1ToL2Messages.flatMap(key => (key.isZero() ? [] : key.toString())), - ), - makeL1ToL2MessageAddedEvents( - 100n, - blocks[1].body.l1ToL2Messages.flatMap(key => (key.isZero() ? [] : key.toString())), - ), - makeL1ToL2MessageAddedEvents( - 2501n, - blocks[2].body.l1ToL2Messages.flatMap(key => (key.isZero() ? [] : key.toString())), - ), - makeL1ToL2MessageAddedEvents(2502n, [ - messageToCancel1, - messageToCancel2, - messageToStayPending1, - messageToStayPending2, - ]), - ]; publicClient.getBlockNumber.mockResolvedValueOnce(2500n).mockResolvedValueOnce(2600n).mockResolvedValueOnce(2700n); // logs should be created in order of how archiver syncs. publicClient.getLogs - .mockResolvedValueOnce(l1ToL2MessageAddedEvents.slice(0, 2).flat()) - .mockResolvedValueOnce([]) // no messages to cancel .mockResolvedValueOnce([makeLeafInsertedEvent(98n, 1n, 0n), makeLeafInsertedEvent(99n, 1n, 1n)]) .mockResolvedValueOnce([makeTxsPublishedEvent(101n, blocks[0].body.getTxsEffectsHash())]) .mockResolvedValueOnce([makeL2BlockProcessedEvent(101n, 1n)]) - .mockResolvedValueOnce(l1ToL2MessageAddedEvents.slice(2, 4).flat()) - .mockResolvedValueOnce(makeL1ToL2MessageCancelledEvents(2503n, l1ToL2MessagesToCancel)) .mockResolvedValueOnce([ makeLeafInsertedEvent(2504n, 2n, 0n), makeLeafInsertedEvent(2505n, 2n, 1n), @@ -116,31 +77,25 @@ describe('Archiver', () => { latestBlockNum = await archiver.getBlockNumber(); expect(latestBlockNum).toEqual(3); - // New L1 to L2 messages + // L1 to L2 messages { // Checks that I get correct amount of sequenced new messages for L2 blocks 1 and 2 - let newL1ToL2Messages = await archiver.getNewL1ToL2Messages(1n); - expect(newL1ToL2Messages.length).toEqual(2); + let l1ToL2Messages = await archiver.getL1ToL2Messages(1n); + expect(l1ToL2Messages.length).toEqual(2); - newL1ToL2Messages = await archiver.getNewL1ToL2Messages(2n); - expect(newL1ToL2Messages.length).toEqual(3); + l1ToL2Messages = await archiver.getL1ToL2Messages(2n); + expect(l1ToL2Messages.length).toEqual(3); // Check that I cannot get messages for block 3 because there is a message gap (message with index 0 was not - // processed) + // processed) --> since we are fetching events individually for each message there is a message gap check when + // fetching the messages for the block in order to ensure that all the messages were really obtained. E.g. if we + // receive messages with indices 0, 1, 2, 4, 5, 6 we can be sure there is an issue because we are missing message + // with index 3. await expect(async () => { - await archiver.getNewL1ToL2Messages(3n); + await archiver.getL1ToL2Messages(3n); }).rejects.toThrow(`L1 to L2 message gap found in block ${3}`); } - // Check that only 2 messages (l1ToL2MessageAddedEvents[3][2] and l1ToL2MessageAddedEvents[3][3]) are pending. - // Other two (l1ToL2MessageAddedEvents[3][0..2]) were cancelled. And the previous messages were confirmed. - const expectedPendingEntryKeys = [ - l1ToL2MessageAddedEvents[3][2].args.entryKey, - l1ToL2MessageAddedEvents[3][3].args.entryKey, - ]; - const actualPendingEntryKeys = (await archiver.getPendingL1ToL2EntryKeys(10)).map(key => key.toString()); - expect(expectedPendingEntryKeys).toEqual(actualPendingEntryKeys); - // Expect logs to correspond to what is set by L2Block.random(...) const encryptedLogs = await archiver.getLogs(1, 100, LogType.ENCRYPTED); expect(encryptedLogs.length).toEqual(blockNumbers.length); @@ -170,7 +125,6 @@ describe('Archiver', () => { rollupAddress, availabilityOracleAddress, inboxAddress, - newInboxAddress, registryAddress, archiverStore, 1000, @@ -179,48 +133,15 @@ describe('Archiver', () => { let latestBlockNum = await archiver.getBlockNumber(); expect(latestBlockNum).toEqual(0); - const createL1ToL2Messages = () => { - return [Fr.random().toString(), Fr.random().toString()]; - }; - const blocks = blockNumbers.map(x => L2Block.random(x, 4, x, x + 1, x * 2, x * 3)); const publishTxs = blocks.map(block => block.body).map(makePublishTx); const rollupTxs = blocks.map(makeRollupTx); - // `L2Block.random(x)` creates some l1 to l2 messages. We add those, - // since it is expected by the test that these would be consumed. - // Archiver removes such messages from pending store. - // Also create some more messages to cancel and some that will stay pending. - - const additionalL1ToL2MessagesBlock102 = createL1ToL2Messages(); - const additionalL1ToL2MessagesBlock103 = createL1ToL2Messages(); - - const l1ToL2MessageAddedEvents = [ - makeL1ToL2MessageAddedEvents( - 100n, - blocks[0].body.l1ToL2Messages.flatMap(key => (key.isZero() ? [] : key.toString())), - ), - makeL1ToL2MessageAddedEvents( - 101n, - blocks[1].body.l1ToL2Messages.flatMap(key => (key.isZero() ? [] : key.toString())), - ), - makeL1ToL2MessageAddedEvents(102n, additionalL1ToL2MessagesBlock102), - makeL1ToL2MessageAddedEvents(103n, additionalL1ToL2MessagesBlock103), - ]; - // Here we set the current L1 block number to 102. L1 to L2 messages after this should not be read. publicClient.getBlockNumber.mockResolvedValue(102n); // add all of the L1 to L2 messages to the mock publicClient.getLogs - .mockImplementationOnce((args?: any) => { - return Promise.resolve( - l1ToL2MessageAddedEvents - .flat() - .filter(x => x.blockNumber! >= args.fromBlock && x.blockNumber! < args.toBlock), - ); - }) - .mockResolvedValueOnce([]) .mockResolvedValueOnce([makeLeafInsertedEvent(66n, 1n, 0n), makeLeafInsertedEvent(68n, 1n, 1n)]) .mockResolvedValueOnce([ makeTxsPublishedEvent(70n, blocks[0].body.getTxsEffectsHash()), @@ -241,65 +162,6 @@ describe('Archiver', () => { latestBlockNum = await archiver.getBlockNumber(); expect(latestBlockNum).toEqual(numL2BlocksInTest); - // Check that the only pending L1 to L2 messages are those from eth bock 102 - const expectedPendingEntryKeys = additionalL1ToL2MessagesBlock102; - const actualPendingEntryKeys = (await archiver.getPendingL1ToL2EntryKeys(100)).map(key => key.toString()); - expect(actualPendingEntryKeys).toEqual(expectedPendingEntryKeys); - - await archiver.stop(); - }, 10_000); - - it('pads L1 to L2 messages', async () => { - const archiver = new Archiver( - publicClient, - rollupAddress, - availabilityOracleAddress, - inboxAddress, - newInboxAddress, - registryAddress, - archiverStore, - 1000, - ); - - let latestBlockNum = await archiver.getBlockNumber(); - expect(latestBlockNum).toEqual(0); - - const block = L2Block.random(1, 4, 1, 2, 4, 6); - const rollupTx = makeRollupTx(block); - const publishTx = makePublishTx(block.body); - - publicClient.getBlockNumber.mockResolvedValueOnce(2500n); - // logs should be created in order of how archiver syncs. - publicClient.getLogs - .mockResolvedValueOnce( - makeL1ToL2MessageAddedEvents( - 100n, - block.body.l1ToL2Messages.map(x => x.toString()), - ), - ) - .mockResolvedValueOnce([]) - .mockResolvedValueOnce([]) - .mockResolvedValueOnce([makeTxsPublishedEvent(101n, block.body.getTxsEffectsHash())]) - .mockResolvedValueOnce([makeL2BlockProcessedEvent(101n, 1n)]) - .mockResolvedValue([]); - publicClient.getTransaction.mockResolvedValueOnce(publishTx); - publicClient.getTransaction.mockResolvedValueOnce(rollupTx); - - await archiver.start(false); - - // Wait until block 1 is processed. If this won't happen the test will fail with timeout. - while ((await archiver.getBlockNumber()) !== 1) { - await sleep(100); - } - - latestBlockNum = await archiver.getBlockNumber(); - expect(latestBlockNum).toEqual(1); - - const expectedL1Messages = block.body.l1ToL2Messages.map(x => x.value); - const receivedBlock = await archiver.getBlock(1); - - expect(receivedBlock?.body.l1ToL2Messages.map(x => x.value)).toEqual(expectedL1Messages); - await archiver.stop(); }, 10_000); }); @@ -333,50 +195,6 @@ function makeTxsPublishedEvent(l1BlockNum: bigint, txsEffectsHash: Buffer) { } as Log; } -/** - * Makes fake L1ToL2 MessageAdded events for testing purposes. - * @param l1BlockNum - L1 block number. - * @param entryKeys - The entry keys of the messages to add. - * @returns MessageAdded event logs. - */ -function makeL1ToL2MessageAddedEvents(l1BlockNum: bigint, entryKeys: string[]) { - return entryKeys.map(entryKey => { - return { - blockNumber: l1BlockNum, - args: { - sender: EthAddress.random().toString(), - senderChainId: 1n, - recipient: AztecAddress.random().toString(), - recipientVersion: 1n, - content: Fr.random().toString(), - secretHash: Fr.random().toString(), - deadline: 100, - fee: 1n, - entryKey: entryKey, - }, - transactionHash: `0x${l1BlockNum}`, - } as Log; - }); -} - -/** - * Makes fake L1ToL2 MessageCancelled events for testing purposes. - * @param l1BlockNum - L1 block number. - * @param entryKey - The entry keys of the message to cancel. - * @returns MessageCancelled event logs. - */ -function makeL1ToL2MessageCancelledEvents(l1BlockNum: bigint, entryKeys: string[]) { - return entryKeys.map(entryKey => { - return { - blockNumber: l1BlockNum, - args: { - entryKey, - }, - transactionHash: `0x${l1BlockNum}`, - } as Log; - }); -} - /** * Makes fake L1ToL2 LeafInserted events for testing purposes. * @param l1BlockNum - L1 block number. @@ -392,7 +210,7 @@ function makeLeafInsertedEvent(l1BlockNum: bigint, l2BlockNumber: bigint, index: value: Fr.random().toString(), }, transactionHash: `0x${l1BlockNum}`, - } as Log; + } as Log; } /** diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index a1e4b4d017f..395c227343e 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -1,6 +1,5 @@ import { GetUnencryptedLogsResponse, - L1ToL2Message, L1ToL2MessageSource, L2Block, L2BlockL2Logs, @@ -21,7 +20,6 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; -import { RollupAbi } from '@aztec/l1-artifacts'; import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer'; import { ContractClassPublic, @@ -30,16 +28,14 @@ import { PublicFunction, } from '@aztec/types/contracts'; -import { Chain, HttpTransport, PublicClient, createPublicClient, getAddress, getContract, http } from 'viem'; +import { Chain, HttpTransport, PublicClient, createPublicClient, http } from 'viem'; import { ArchiverDataStore } from './archiver_store.js'; import { ArchiverConfig } from './config.js'; import { retrieveBlockBodiesFromAvailabilityOracle, retrieveBlockMetadataFromRollup, - retrieveNewCancelledL1ToL2Messages, - retrieveNewL1ToL2Messages, - retrieveNewPendingL1ToL2Messages, + retrieveL1ToL2Messages, } from './data_retrieval.js'; /** @@ -68,7 +64,6 @@ export class Archiver implements ArchiveSource { * @param publicClient - A client for interacting with the Ethereum node. * @param rollupAddress - Ethereum address of the rollup contract. * @param inboxAddress - Ethereum address of the inbox contract. - * @param newInboxAddress - Ethereum address of the new inbox contract. * @param registryAddress - Ethereum address of the registry contract. * @param pollingIntervalMs - The interval for polling for L1 logs (in milliseconds). * @param store - An archiver data store for storage & retrieval of blocks, encrypted logs & contract data. @@ -79,7 +74,6 @@ export class Archiver implements ArchiveSource { private readonly rollupAddress: EthAddress, private readonly availabilityOracleAddress: EthAddress, private readonly inboxAddress: EthAddress, - private readonly newInboxAddress: EthAddress, private readonly registryAddress: EthAddress, private readonly store: ArchiverDataStore, private readonly pollingIntervalMs = 10_000, @@ -105,23 +99,11 @@ export class Archiver implements ArchiveSource { pollingInterval: config.viemPollingIntervalMS, }); - // TODO(#4492): Nuke this once the old inbox is purged - let newInboxAddress!: EthAddress; - { - const rollup = getContract({ - address: getAddress(config.l1Contracts.rollupAddress.toString()), - abi: RollupAbi, - client: publicClient, - }); - newInboxAddress = EthAddress.fromString(await rollup.read.NEW_INBOX()); - } - const archiver = new Archiver( publicClient, config.l1Contracts.rollupAddress, config.l1Contracts.availabilityOracleAddress, config.l1Contracts.inboxAddress, - newInboxAddress, config.l1Contracts.registryAddress, archiverStore, config.archiverPollingIntervalMS, @@ -165,15 +147,10 @@ export class Archiver implements ArchiveSource { * * This code does not handle reorgs. */ - const lastL1Blocks = await this.store.getL1BlockNumber(); + const lastL1Blocks = await this.store.getSynchedL1BlockNumbers(); const currentL1BlockNumber = await this.publicClient.getBlockNumber(); - if ( - currentL1BlockNumber <= lastL1Blocks.addedBlock && - currentL1BlockNumber <= lastL1Blocks.newMessages && - currentL1BlockNumber <= lastL1Blocks.addedMessages && - currentL1BlockNumber <= lastL1Blocks.cancelledMessages - ) { + if (currentL1BlockNumber <= lastL1Blocks.blocks && currentL1BlockNumber <= lastL1Blocks.messages) { // chain hasn't moved forward // or it's been rolled back return; @@ -200,72 +177,39 @@ export class Archiver implements ArchiveSource { // ********** Events that are processed per L1 block ********** - // TODO(#4492): Nuke the following when purging the old inbox - // Process l1ToL2Messages, these are consumed as time passes, not each block - const retrievedPendingL1ToL2Messages = await retrieveNewPendingL1ToL2Messages( - this.publicClient, - this.inboxAddress, - blockUntilSynced, - lastL1Blocks.addedMessages + 1n, - currentL1BlockNumber, - ); - const retrievedCancelledL1ToL2Messages = await retrieveNewCancelledL1ToL2Messages( + // ********** Events that are processed per L2 block ********** + + const retrievedL1ToL2Messages = await retrieveL1ToL2Messages( this.publicClient, this.inboxAddress, blockUntilSynced, - lastL1Blocks.cancelledMessages + 1n, + lastL1Blocks.messages + 1n, currentL1BlockNumber, ); - // group pending messages and cancelled messages by their L1 block number - const messagesByBlock = new Map(); - for (const [message, blockNumber] of retrievedPendingL1ToL2Messages.retrievedData) { - const messages = messagesByBlock.get(blockNumber) || [[], []]; - messages[0].push(message); - messagesByBlock.set(blockNumber, messages); - } - - for (const [entryKey, blockNumber] of retrievedCancelledL1ToL2Messages.retrievedData) { - const messages = messagesByBlock.get(blockNumber) || [[], []]; - messages[1].push(entryKey); - messagesByBlock.set(blockNumber, messages); - } - - // process messages from each L1 block in sequence - const l1BlocksWithMessages = Array.from(messagesByBlock.keys()).sort((a, b) => (a < b ? -1 : a === b ? 0 : 1)); - for (const l1Block of l1BlocksWithMessages) { - const [newMessages, cancelledMessages] = messagesByBlock.get(l1Block)!; + if (retrievedL1ToL2Messages.retrievedData.length !== 0) { this.log( - `Adding ${newMessages.length} new messages and ${cancelledMessages.length} cancelled messages in L1 block ${l1Block}`, + `Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 -> L2 messages between L1 blocks ${ + lastL1Blocks.messages + 1n + } and ${currentL1BlockNumber}.`, ); - await this.store.addPendingL1ToL2Messages(newMessages, l1Block); - await this.store.cancelPendingL1ToL2EntryKeys(cancelledMessages, l1Block); } - // ********** Events that are processed per L2 block ********** - - const retrievedNewL1ToL2Messages = await retrieveNewL1ToL2Messages( - this.publicClient, - this.newInboxAddress, - blockUntilSynced, - lastL1Blocks.newMessages + 1n, - currentL1BlockNumber, - ); - await this.store.addNewL1ToL2Messages( - retrievedNewL1ToL2Messages.retrievedData, + await this.store.addL1ToL2Messages( + retrievedL1ToL2Messages.retrievedData, // -1n because the function expects the last block in which the message was emitted and not the one after next - // TODO(#4492): Check whether this could be cleaned up - `nextEthBlockNumber` value doesn't seem to be used much - retrievedNewL1ToL2Messages.nextEthBlockNumber - 1n, + // TODO(#5264): Check whether this could be cleaned up - `nextEthBlockNumber` value doesn't seem to be used much + retrievedL1ToL2Messages.nextEthBlockNumber - 1n, ); // Read all data from chain and then write to our stores at the end - const nextExpectedL2BlockNum = BigInt((await this.store.getBlockNumber()) + 1); + const nextExpectedL2BlockNum = BigInt((await this.store.getSynchedL2BlockNumber()) + 1); const retrievedBlockBodies = await retrieveBlockBodiesFromAvailabilityOracle( this.publicClient, this.availabilityOracleAddress, blockUntilSynced, - lastL1Blocks.addedBlock + 1n, + lastL1Blocks.blocks + 1n, currentL1BlockNumber, ); @@ -277,7 +221,7 @@ export class Archiver implements ArchiveSource { this.publicClient, this.rollupAddress, blockUntilSynced, - lastL1Blocks.addedBlock + 1n, + lastL1Blocks.blocks + 1n, currentL1BlockNumber, nextExpectedL2BlockNum, ); @@ -304,7 +248,7 @@ export class Archiver implements ArchiveSource { } else { this.log( `Retrieved ${retrievedBlocks.retrievedData.length} new L2 blocks between L1 blocks ${ - lastL1Blocks.addedBlock + 1n + lastL1Blocks.blocks + 1n } and ${currentL1BlockNumber}.`, ); } @@ -315,8 +259,6 @@ export class Archiver implements ArchiveSource { blockNumberToBodyHash[block.number] = block.header.contentCommitment.txsEffectsHash; }); - this.log(`Retrieved ${retrievedBlocks.retrievedData.length} block(s) from chain`); - await Promise.all( retrievedBlocks.retrievedData.map(block => { const encryptedLogs = block.body.encryptedLogs; @@ -338,13 +280,6 @@ export class Archiver implements ArchiveSource { }), ); - // from retrieved L2Blocks, confirm L1 to L2 messages that have been published - // from each l2block fetch all entryKeys in a flattened array: - this.log(`Confirming l1 to l2 messages in store`); - for (const block of retrievedBlocks.retrievedData) { - await this.store.confirmL1ToL2EntryKeys(block.body.l1ToL2Messages); - } - await this.store.addBlocks(retrievedBlocks.retrievedData); } @@ -412,7 +347,7 @@ export class Archiver implements ArchiveSource { public async getBlock(number: number): Promise { // If the number provided is -ve, then return the latest block. if (number < 0) { - number = await this.store.getBlockNumber(); + number = await this.store.getSynchedL2BlockNumber(); } const blocks = await this.store.getBlocks(number, 1); return blocks.length === 0 ? undefined : blocks[0]; @@ -472,7 +407,7 @@ export class Archiver implements ArchiveSource { * @returns The number of the latest L2 block processed by the block source implementation. */ public getBlockNumber(): Promise { - return this.store.getBlockNumber(); + return this.store.getSynchedL2BlockNumber(); } public getContractClass(id: Fr): Promise { @@ -484,30 +419,21 @@ export class Archiver implements ArchiveSource { } /** - * Gets up to `limit` amount of pending L1 to L2 messages. - * @param limit - The number of messages to return. - * @returns The requested L1 to L2 messages' keys. - */ - getPendingL1ToL2EntryKeys(limit: number): Promise { - return this.store.getPendingL1ToL2EntryKeys(limit); - } - - /** - * Gets the confirmed/consumed L1 to L2 message associated with the given entry key - * @param entryKey - The entry key. - * @returns The L1 to L2 message (throws if not found). + * Gets L1 to L2 message (to be) included in a given block. + * @param blockNumber - L2 block number to get messages for. + * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). */ - getConfirmedL1ToL2Message(entryKey: Fr): Promise { - return this.store.getConfirmedL1ToL2Message(entryKey); + getL1ToL2Messages(blockNumber: bigint): Promise { + return this.store.getL1ToL2Messages(blockNumber); } /** - * Gets new L1 to L2 message (to be) included in a given block. - * @param blockNumber - L2 block number to get messages for. - * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - getNewL1ToL2Messages(blockNumber: bigint): Promise { - return this.store.getNewL1ToL2Messages(blockNumber); + getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise { + return this.store.getL1ToL2MessageIndex(l1ToL2Message); } getContractClassIds(): Promise { diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 356c785248f..30876c75e9b 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -1,12 +1,11 @@ import { Body, GetUnencryptedLogsResponse, - L1ToL2Message, + InboxLeaf, L2Block, L2BlockL2Logs, LogFilter, LogType, - NewInboxLeaf, TxEffect, TxHash, TxReceipt, @@ -20,14 +19,9 @@ import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/c */ export type ArchiverL1SynchPoint = { /** The last L1 block that added a new L2 block. */ - addedBlock: bigint; - /** The last L1 block that added messages from the new inbox. */ - // TODO(#4492): Clean this up and fix the naming - newMessages: bigint; - /** The last L1 block that added pending messages */ - addedMessages: bigint; - /** The last L1 block that cancelled messages */ - cancelledMessages: bigint; + blocks: bigint; + /** The last L1 block that added L1 -> L2 messages from the Inbox. */ + messages: bigint; }; /** @@ -93,59 +87,26 @@ export interface ArchiverDataStore { ): Promise; /** - * Append new L1 to L2 messages to the store. + * Append L1 to L2 messages to the store. * @param messages - The L1 to L2 messages to be added to the store. * @param lastMessageL1BlockNumber - The L1 block number in which the last message was emitted. * @returns True if the operation is successful. */ - addNewL1ToL2Messages(messages: NewInboxLeaf[], lastMessageL1BlockNumber: bigint): Promise; + addL1ToL2Messages(messages: InboxLeaf[], lastMessageL1BlockNumber: bigint): Promise; /** - * Append new pending L1 to L2 messages to the store. - * @param messages - The L1 to L2 messages to be added to the store. - * @param l1BlockNumber - The block number of the L1 block that added the messages. - * @returns True if the operation is successful. - * TODO(#4492): Nuke the following when purging the old inbox - */ - addPendingL1ToL2Messages(messages: L1ToL2Message[], l1BlockNumber: bigint): Promise; - - /** - * Remove pending L1 to L2 messages from the store (if they were cancelled). - * @param entryKeys - The entry keys to be removed from the store. - * @param l1BlockNumber - The block number of the L1 block that cancelled the messages. - * @returns True if the operation is successful. - * TODO(#4492): Nuke the following when purging the old inbox - */ - cancelPendingL1ToL2EntryKeys(entryKeys: Fr[], l1BlockNumber: bigint): Promise; - - /** - * Messages that have been published in an L2 block are confirmed. - * Add them to the confirmed store, also remove them from the pending store. - * @param entryKeys - The entry keys to be removed from the store. - * @returns True if the operation is successful. - */ - confirmL1ToL2EntryKeys(entryKeys: Fr[]): Promise; - - /** - * Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee - * @param limit - The number of entries to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). - * @returns The requested L1 to L2 entry keys. - */ - getPendingL1ToL2EntryKeys(limit: number): Promise; - - /** - * Gets the confirmed L1 to L2 message corresponding to the given entry key. - * @param entryKey - The entry key to look up. - * @returns The requested L1 to L2 message or throws if not found. + * Gets L1 to L2 message (to be) included in a given block. + * @param blockNumber - L2 block number to get messages for. + * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). */ - getConfirmedL1ToL2Message(entryKey: Fr): Promise; + getL1ToL2Messages(blockNumber: bigint): Promise; /** - * Gets new L1 to L2 message (to be) included in a given block. - * @param blockNumber - L2 block number to get messages for. - * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - getNewL1ToL2Messages(blockNumber: bigint): Promise; + getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise; /** * Gets up to `limit` amount of logs starting from `from`. @@ -167,12 +128,12 @@ export interface ArchiverDataStore { * Gets the number of the latest L2 block processed. * @returns The number of the latest L2 block processed. */ - getBlockNumber(): Promise; + getSynchedL2BlockNumber(): Promise; /** - * Gets the last L1 block number processed by the archiver + * Gets the synch point of the archiver */ - getL1BlockNumber(): Promise; + getSynchedL1BlockNumbers(): Promise; /** * Add new contract classes 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 9c6117c90a3..f6c669c0f0d 100644 --- a/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts +++ b/yarn-project/archiver/src/archiver/archiver_store_test_suite.ts @@ -1,13 +1,4 @@ -import { - L1ToL2Message, - L2Block, - L2BlockContext, - LogId, - LogType, - NewInboxLeaf, - TxHash, - UnencryptedL2Log, -} from '@aztec/circuit-types'; +import { InboxLeaf, L2Block, L2BlockContext, LogId, LogType, TxHash, UnencryptedL2Log } from '@aztec/circuit-types'; import '@aztec/circuit-types/jest'; import { AztecAddress, Fr, INITIAL_L2_BLOCK_NUM, L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/circuits.js'; import { makeContractClassPublic } from '@aztec/circuits.js/testing'; @@ -79,64 +70,38 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch }); }); - describe('getBlockNumber', () => { + describe('getSyncedL2BlockNumber', () => { it('returns the block number before INITIAL_L2_BLOCK_NUM if no blocks have been added', async () => { - await expect(store.getBlockNumber()).resolves.toEqual(INITIAL_L2_BLOCK_NUM - 1); + await expect(store.getSynchedL2BlockNumber()).resolves.toEqual(INITIAL_L2_BLOCK_NUM - 1); }); it("returns the most recently added block's number", async () => { await store.addBlocks(blocks); - await expect(store.getBlockNumber()).resolves.toEqual(blocks.at(-1)!.number); + await expect(store.getSynchedL2BlockNumber()).resolves.toEqual(blocks.at(-1)!.number); }); }); - describe('getL1BlockNumber', () => { + describe('getSynchedL1BlockNumbers', () => { it('returns 0n if no blocks have been added', async () => { - await expect(store.getL1BlockNumber()).resolves.toEqual({ - addedBlock: 0n, - addedMessages: 0n, - cancelledMessages: 0n, - newMessages: 0n, + await expect(store.getSynchedL1BlockNumbers()).resolves.toEqual({ + blocks: 0n, + messages: 0n, }); }); it('returns the L1 block number in which the most recent L2 block was published', async () => { await store.addBlocks(blocks); - await expect(store.getL1BlockNumber()).resolves.toEqual({ - addedBlock: blocks.at(-1)!.getL1BlockNumber(), - addedMessages: 0n, - cancelledMessages: 0n, - newMessages: 0n, + await expect(store.getSynchedL1BlockNumbers()).resolves.toEqual({ + blocks: blocks.at(-1)!.getL1BlockNumber(), + messages: 0n, }); }); - it('returns the L1 block number that most recently added pending messages', async () => { - await store.addPendingL1ToL2Messages([L1ToL2Message.random(Fr.random())], 1n); - await expect(store.getL1BlockNumber()).resolves.toEqual({ - addedBlock: 0n, - addedMessages: 1n, - cancelledMessages: 0n, - newMessages: 0n, - }); - }); - it('returns the L1 block number that most recently added messages from new inbox', async () => { - await store.addNewL1ToL2Messages([new NewInboxLeaf(0n, 0n, Fr.ZERO)], 1n); - await expect(store.getL1BlockNumber()).resolves.toEqual({ - addedBlock: 0n, - addedMessages: 0n, - cancelledMessages: 0n, - newMessages: 1n, - }); - }); - it('returns the L1 block number that most recently cancelled pending messages', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 2n); - await expect(store.getL1BlockNumber()).resolves.toEqual({ - addedBlock: 0n, - addedMessages: 1n, - cancelledMessages: 2n, - newMessages: 0n, + it('returns the L1 block number that most recently added messages from inbox', async () => { + await store.addL1ToL2Messages([new InboxLeaf(0n, 0n, Fr.ZERO)], 1n); + await expect(store.getSynchedL1BlockNumbers()).resolves.toEqual({ + blocks: 0n, + messages: 1n, }); }); }); @@ -194,47 +159,18 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch }); }); - describe('addPendingL1ToL2Messages', () => { - it('stores pending L1 to L2 messages', async () => { - await expect(store.addPendingL1ToL2Messages([L1ToL2Message.random(Fr.random())], 1n)).resolves.toEqual(true); - }); - - it('allows duplicate pending messages in different positions in the same block', async () => { - const message = L1ToL2Message.random(Fr.random()); - await expect(store.addPendingL1ToL2Messages([message, message], 1n)).resolves.toEqual(true); - - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([message.entryKey!, message.entryKey!]); - }); - - it('allows duplicate pending messages in different blocks', async () => { - const message = L1ToL2Message.random(Fr.random()); - await expect(store.addPendingL1ToL2Messages([message], 1n)).resolves.toEqual(true); - await expect(store.addPendingL1ToL2Messages([message], 2n)).resolves.toEqual(true); - - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([message.entryKey!, message.entryKey!]); - }); - - it('is idempotent', async () => { - const message = L1ToL2Message.random(Fr.random()); - await expect(store.addPendingL1ToL2Messages([message], 1n)).resolves.toEqual(true); - await expect(store.addPendingL1ToL2Messages([message], 1n)).resolves.toEqual(false); - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([message.entryKey!]); - }); - }); - - // TODO(#4492): Drop the "New" below once the old inbox is purged - describe('New L1 to L2 Messages', () => { + describe('L1 to L2 Messages', () => { const l2BlockNumber = 13n; const l1ToL2MessageSubtreeSize = 2 ** L1_TO_L2_MSG_SUBTREE_HEIGHT; const generateBlockMessages = (blockNumber: bigint, numMessages: number) => - Array.from({ length: numMessages }, (_, i) => new NewInboxLeaf(blockNumber, BigInt(i), Fr.random())); + Array.from({ length: numMessages }, (_, i) => new InboxLeaf(blockNumber, BigInt(i), Fr.random())); it('returns messages in correct order', async () => { const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize); const shuffledMessages = msgs.slice().sort(() => randomInt(1) - 0.5); - await store.addNewL1ToL2Messages(shuffledMessages, 100n); - const retrievedMessages = await store.getNewL1ToL2Messages(l2BlockNumber); + await store.addL1ToL2Messages(shuffledMessages, 100n); + const retrievedMessages = await store.getL1ToL2Messages(l2BlockNumber); const expectedLeavesOrder = msgs.map(msg => msg.leaf); expect(expectedLeavesOrder).toEqual(retrievedMessages); @@ -244,11 +180,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize - 1); // We replace a message with index 4 with a message with index at the end of the tree // --> with that there will be a gap and it will be impossible to sequence the messages - msgs[4] = new NewInboxLeaf(l2BlockNumber, BigInt(l1ToL2MessageSubtreeSize - 1), Fr.random()); + msgs[4] = new InboxLeaf(l2BlockNumber, BigInt(l1ToL2MessageSubtreeSize - 1), Fr.random()); - await store.addNewL1ToL2Messages(msgs, 100n); + await store.addL1ToL2Messages(msgs, 100n); await expect(async () => { - await store.getNewL1ToL2Messages(l2BlockNumber); + await store.getL1ToL2Messages(l2BlockNumber); }).rejects.toThrow(`L1 to L2 message gap found in block ${l2BlockNumber}`); }); @@ -256,130 +192,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize + 1); await expect(async () => { - await store.addNewL1ToL2Messages(msgs, 100n); + await store.addL1ToL2Messages(msgs, 100n); }).rejects.toThrow(`Message index ${l1ToL2MessageSubtreeSize} out of subtree range`); }); }); - describe('getPendingL1ToL2EntryKeys', () => { - it('returns previously stored pending L1 to L2 messages', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([message.entryKey!]); - }); - - // TODO(@spalladino): Fix and re-enable - it.skip('returns messages ordered by fee', async () => { - const messages = Array.from({ length: 3 }, () => L1ToL2Message.random(Fr.random())); - // add a duplicate message - messages.push(messages[0]); - - await store.addPendingL1ToL2Messages(messages, 1n); - - messages.sort((a, b) => b.fee - a.fee); - await expect(store.getPendingL1ToL2EntryKeys(messages.length)).resolves.toEqual( - messages.map(message => message.entryKey!), - ); - }); - - it('returns an empty array if no messages are found', async () => { - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([]); - }); - }); - - describe('confirmL1ToL2EntryKeys', () => { - it('updates a message from pending to confirmed', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await expect(store.confirmL1ToL2EntryKeys([message.entryKey!])).resolves.toEqual(true); - }); - - it('once confirmed, a message is no longer pending', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await store.confirmL1ToL2EntryKeys([message.entryKey!]); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([]); - }); - - it('once confirmed a message can also be pending if added again', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await store.confirmL1ToL2EntryKeys([message.entryKey!]); - await store.addPendingL1ToL2Messages([message], 2n); - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([message.entryKey!]); - }); - - it('once confirmed a message can remain pending if more of it were pending', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message, message], 1n); - await store.confirmL1ToL2EntryKeys([message.entryKey!]); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([message.entryKey!]); - }); - }); - - describe('cancelL1ToL2Messages', () => { - it('cancels a pending message', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 1n); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([]); - }); - - it('cancels only one of the pending messages if duplicates exist', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message, message], 1n); - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 1n); - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([message.entryKey]); - }); - - it('once canceled a message can also be pending if added again', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 1n); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([]); - - await store.addPendingL1ToL2Messages([message], 2n); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([message.entryKey!]); - }); - - it('allows adding and cancelling in the same block', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message], 1n); - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 1n); - await expect(store.getPendingL1ToL2EntryKeys(1)).resolves.toEqual([]); - }); - - it('allows duplicates cancellations in different positions in the same block', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message, message], 1n); - - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!, message.entryKey!], 1n); - - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([]); - }); - - it('allows duplicates cancellations in different blocks', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message, message], 1n); - - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 2n); - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 3n); - - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([]); - }); - - it('is idempotent', async () => { - const message = L1ToL2Message.random(Fr.random()); - await store.addPendingL1ToL2Messages([message, message], 1n); - - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 2n); - await store.cancelPendingL1ToL2EntryKeys([message.entryKey!], 2n); - - await expect(store.getPendingL1ToL2EntryKeys(2)).resolves.toEqual([message.entryKey!]); - }); - }); - describe('contractInstances', () => { let contractInstance: ContractInstanceWithAddress; const blockNum = 10; diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index 8c90b82e147..25d4f90be2d 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -1,19 +1,15 @@ -import { Body, L1ToL2Message, NewInboxLeaf } from '@aztec/circuit-types'; -import { AppendOnlyTreeSnapshot, Fr, Header } from '@aztec/circuits.js'; +import { Body, InboxLeaf } from '@aztec/circuit-types'; +import { AppendOnlyTreeSnapshot, Header } from '@aztec/circuits.js'; import { EthAddress } from '@aztec/foundation/eth-address'; import { PublicClient } from 'viem'; import { - getL1ToL2MessageCancelledLogs, getL2BlockProcessedLogs, getLeafInsertedLogs, - getPendingL1ToL2MessageLogs, getTxsPublishedLogs, - processCancelledL1ToL2MessagesLogs, processL2BlockProcessedLogs, processLeafInsertedLogs, - processPendingL1ToL2MessageAddedLogs, processTxsPublishedLogs, } from './eth_log_handlers.js'; @@ -116,112 +112,34 @@ export async function retrieveBlockBodiesFromAvailabilityOracle( } /** - * Fetch new pending L1 to L2 messages. + * Fetch L1 to L2 messages. * @param publicClient - The viem public client to use for transaction retrieval. * @param inboxAddress - The address of the inbox contract to fetch messages from. * @param blockUntilSynced - If true, blocks until the archiver has fully synced. * @param searchStartBlock - The block number to use for starting the search. * @param searchEndBlock - The highest block number that we should search up to. - * @returns An array of L1ToL2Message and next eth block to search from. + * @returns An array of InboxLeaf and next eth block to search from. */ -export async function retrieveNewPendingL1ToL2Messages( +export async function retrieveL1ToL2Messages( publicClient: PublicClient, inboxAddress: EthAddress, blockUntilSynced: boolean, searchStartBlock: bigint, searchEndBlock: bigint, -): Promise> { - const retrievedNewL1ToL2Messages: [L1ToL2Message, bigint][] = []; +): Promise> { + const retrievedL1ToL2Messages: InboxLeaf[] = []; do { if (searchStartBlock > searchEndBlock) { break; } - const newL1ToL2MessageLogs = await getPendingL1ToL2MessageLogs( - publicClient, - inboxAddress, - searchStartBlock, - searchEndBlock, - ); - if (newL1ToL2MessageLogs.length === 0) { - break; - } - const newL1ToL2Messages = processPendingL1ToL2MessageAddedLogs(newL1ToL2MessageLogs); - retrievedNewL1ToL2Messages.push(...newL1ToL2Messages); - // handles the case when there are no new messages: - searchStartBlock = (newL1ToL2MessageLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n; - } while (blockUntilSynced && searchStartBlock <= searchEndBlock); - return { nextEthBlockNumber: searchStartBlock, retrievedData: retrievedNewL1ToL2Messages }; -} - -/** - * Fetch new L1 to L2 messages. - * @param publicClient - The viem public client to use for transaction retrieval. - * @param newInboxAddress - The address of the inbox contract to fetch messages from. - * @param blockUntilSynced - If true, blocks until the archiver has fully synced. - * @param searchStartBlock - The block number to use for starting the search. - * @param searchEndBlock - The highest block number that we should search up to. - * @returns An array of NewInboxLeaf and next eth block to search from. - */ -export async function retrieveNewL1ToL2Messages( - publicClient: PublicClient, - newInboxAddress: EthAddress, - blockUntilSynced: boolean, - searchStartBlock: bigint, - searchEndBlock: bigint, -): Promise> { - const retrievedNewL1ToL2Messages: NewInboxLeaf[] = []; - do { - if (searchStartBlock > searchEndBlock) { - break; - } - const leafInsertedLogs = await getLeafInsertedLogs(publicClient, newInboxAddress, searchStartBlock, searchEndBlock); + const leafInsertedLogs = await getLeafInsertedLogs(publicClient, inboxAddress, searchStartBlock, searchEndBlock); if (leafInsertedLogs.length === 0) { break; } - const newL1ToL2Messages = processLeafInsertedLogs(leafInsertedLogs); - retrievedNewL1ToL2Messages.push(...newL1ToL2Messages); + const l1ToL2Messages = processLeafInsertedLogs(leafInsertedLogs); + retrievedL1ToL2Messages.push(...l1ToL2Messages); // handles the case when there are no new messages: searchStartBlock = (leafInsertedLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n; } while (blockUntilSynced && searchStartBlock <= searchEndBlock); - return { nextEthBlockNumber: searchStartBlock, retrievedData: retrievedNewL1ToL2Messages }; -} - -/** - * Fetch newly cancelled L1 to L2 messages. - * @param publicClient - The viem public client to use for transaction retrieval. - * @param inboxAddress - The address of the inbox contract to fetch messages from. - * @param blockUntilSynced - If true, blocks until the archiver has fully synced. - * @param searchStartBlock - The block number to use for starting the search. - * @param searchEndBlock - The highest block number that we should search up to. - * @returns An array of entry keys that were cancelled and next eth block to search from. - * TODO(#4492): Nuke the following when purging the old inbox - */ -export async function retrieveNewCancelledL1ToL2Messages( - publicClient: PublicClient, - inboxAddress: EthAddress, - blockUntilSynced: boolean, - searchStartBlock: bigint, - searchEndBlock: bigint, -): Promise> { - const retrievedNewCancelledL1ToL2Messages: [Fr, bigint][] = []; - do { - if (searchStartBlock > searchEndBlock) { - break; - } - const newL1ToL2MessageCancelledLogs = await getL1ToL2MessageCancelledLogs( - publicClient, - inboxAddress, - searchStartBlock, - searchEndBlock, - ); - if (newL1ToL2MessageCancelledLogs.length === 0) { - break; - } - const newCancelledL1ToL2Messages = processCancelledL1ToL2MessagesLogs(newL1ToL2MessageCancelledLogs); - retrievedNewCancelledL1ToL2Messages.push(...newCancelledL1ToL2Messages); - // handles the case when there are no new messages: - searchStartBlock = - (newL1ToL2MessageCancelledLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n; - } while (blockUntilSynced && searchStartBlock <= searchEndBlock); - return { nextEthBlockNumber: searchStartBlock, retrievedData: retrievedNewCancelledL1ToL2Messages }; + return { nextEthBlockNumber: searchStartBlock, retrievedData: retrievedL1ToL2Messages }; } diff --git a/yarn-project/archiver/src/archiver/eth_log_handlers.ts b/yarn-project/archiver/src/archiver/eth_log_handlers.ts index 3dfac303a04..b20b770973b 100644 --- a/yarn-project/archiver/src/archiver/eth_log_handlers.ts +++ b/yarn-project/archiver/src/archiver/eth_log_handlers.ts @@ -1,10 +1,9 @@ -import { Body, L1Actor, L1ToL2Message, L2Actor, NewInboxLeaf } from '@aztec/circuit-types'; +import { Body, InboxLeaf } from '@aztec/circuit-types'; import { AppendOnlyTreeSnapshot, Header } from '@aztec/circuits.js'; -import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { numToUInt32BE } from '@aztec/foundation/serialize'; -import { AvailabilityOracleAbi, InboxAbi, NewInboxAbi, RollupAbi } from '@aztec/l1-artifacts'; +import { AvailabilityOracleAbi, InboxAbi, RollupAbi } from '@aztec/l1-artifacts'; import { Hex, Log, PublicClient, decodeFunctionData, getAbiItem, getAddress, hexToBytes } from 'viem'; @@ -14,59 +13,16 @@ import { Hex, Log, PublicClient, decodeFunctionData, getAbiItem, getAddress, hex * @returns Array of all processed LeafInserted logs */ export function processLeafInsertedLogs( - logs: Log[], -): NewInboxLeaf[] { - const leaves: NewInboxLeaf[] = []; + logs: Log[], +): InboxLeaf[] { + const leaves: InboxLeaf[] = []; for (const log of logs) { const { blockNumber, index, value } = log.args; - leaves.push(new NewInboxLeaf(blockNumber, index, Fr.fromString(value))); + leaves.push(new InboxLeaf(blockNumber, index, Fr.fromString(value))); } return leaves; } -/** - * Processes newly received MessageAdded (L1 to L2) logs. - * @param logs - MessageAdded logs. - * @returns Array of all Pending L1 to L2 messages that were processed - */ -export function processPendingL1ToL2MessageAddedLogs( - logs: Log[], -): [L1ToL2Message, bigint][] { - const l1ToL2Messages: [L1ToL2Message, bigint][] = []; - for (const log of logs) { - const { sender, senderChainId, recipient, recipientVersion, content, secretHash, deadline, fee, entryKey } = - log.args; - l1ToL2Messages.push([ - new L1ToL2Message( - new L1Actor(EthAddress.fromString(sender), Number(senderChainId)), - new L2Actor(AztecAddress.fromString(recipient), Number(recipientVersion)), - Fr.fromString(content), - Fr.fromString(secretHash), - deadline, - Number(fee), - Fr.fromString(entryKey), - ), - log.blockNumber!, - ]); - } - return l1ToL2Messages; -} - -/** - * Process newly received L1ToL2MessageCancelled logs. - * @param logs - L1ToL2MessageCancelled logs. - * @returns Array of entry keys of the L1 to L2 messages that were cancelled - */ -export function processCancelledL1ToL2MessagesLogs( - logs: Log[], -): [Fr, bigint][] { - const cancelledL1ToL2Messages: [Fr, bigint][] = []; - for (const log of logs) { - cancelledL1ToL2Messages.push([Fr.fromString(log.args.entryKey), log.blockNumber!]); - } - return cancelledL1ToL2Messages; -} - /** * Processes newly received L2BlockProcessed logs. * @param publicClient - The viem public client to use for transaction retrieval. @@ -235,73 +191,23 @@ export function getTxsPublishedLogs( } /** - * Get relevant `MessageAdded` logs emitted by Inbox on chain. - * @param publicClient - The viem public client to use for transaction retrieval. - * @param inboxAddress - The address of the inbox contract. - * @param fromBlock - First block to get logs from (inclusive). - * @param toBlock - Last block to get logs from (inclusive). - * @returns An array of `MessageAdded` logs. - */ -export function getPendingL1ToL2MessageLogs( - publicClient: PublicClient, - inboxAddress: EthAddress, - fromBlock: bigint, - toBlock: bigint, -): Promise[]> { - return publicClient.getLogs({ - address: getAddress(inboxAddress.toString()), - event: getAbiItem({ - abi: InboxAbi, - name: 'MessageAdded', - }), - fromBlock, - toBlock: toBlock + 1n, // the toBlock argument in getLogs is exclusive - }); -} - -/** - * Get relevant `L1ToL2MessageCancelled` logs emitted by Inbox on chain when pending messages are cancelled + * Get relevant `LeafInserted` logs emitted by Inbox on chain. * @param publicClient - The viem public client to use for transaction retrieval. * @param inboxAddress - The address of the inbox contract. * @param fromBlock - First block to get logs from (inclusive). * @param toBlock - Last block to get logs from (inclusive). - * @returns An array of `L1ToL2MessageCancelled` logs. + * @returns An array of `LeafInserted` logs. */ -export function getL1ToL2MessageCancelledLogs( +export function getLeafInsertedLogs( publicClient: PublicClient, inboxAddress: EthAddress, fromBlock: bigint, toBlock: bigint, -): Promise[]> { +): Promise[]> { return publicClient.getLogs({ address: getAddress(inboxAddress.toString()), event: getAbiItem({ abi: InboxAbi, - name: 'L1ToL2MessageCancelled', - }), - fromBlock, - toBlock: toBlock + 1n, // the toBlock argument in getLogs is exclusive - }); -} - -/** - * Get relevant `LeafInserted` logs emitted by NewInbox on chain. - * @param publicClient - The viem public client to use for transaction retrieval. - * @param newInboxAddress - The address of the new inbox contract. - * @param fromBlock - First block to get logs from (inclusive). - * @param toBlock - Last block to get logs from (inclusive). - * @returns An array of `LeafInserted` logs. - */ -export function getLeafInsertedLogs( - publicClient: PublicClient, - newInboxAddress: EthAddress, - fromBlock: bigint, - toBlock: bigint, -): Promise[]> { - return publicClient.getLogs({ - address: getAddress(newInboxAddress.toString()), - event: getAbiItem({ - abi: NewInboxAbi, name: 'LeafInserted', }), fromBlock, diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts index 8d26356acab..e4ae4a7b161 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts @@ -155,7 +155,7 @@ export class BlockStore { * Gets the number of the latest L2 block processed. * @returns The number of the latest L2 block processed. */ - getBlockNumber(): number { + getSynchedL2BlockNumber(): number { const [lastBlockNumber] = this.#blocks.keys({ reverse: true, limit: 1 }); return typeof lastBlockNumber === 'number' ? lastBlockNumber : INITIAL_L2_BLOCK_NUM - 1; } @@ -164,7 +164,7 @@ export class BlockStore { * Gets the most recent L1 block processed. * @returns The L1 block that published the latest L2 block */ - getL1BlockNumber(): bigint { + getSynchedL1BlockNumber(): bigint { const [lastBlock] = this.#blocks.values({ reverse: true, limit: 1 }); if (!lastBlock) { return 0n; 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 94e4217ba69..834737e5a9d 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 @@ -1,12 +1,11 @@ import { Body, GetUnencryptedLogsResponse, - L1ToL2Message, + InboxLeaf, L2Block, L2BlockL2Logs, LogFilter, LogType, - NewInboxLeaf, TxEffect, TxHash, TxReceipt, @@ -145,76 +144,32 @@ export class KVArchiverDataStore implements ArchiverDataStore { } /** - * Append new L1 to L2 messages to the store. + * Append L1 to L2 messages to the store. * @param messages - The L1 to L2 messages to be added to the store. * @param lastMessageL1BlockNumber - The L1 block number in which the last message was emitted. * @returns True if the operation is successful. */ - addNewL1ToL2Messages(messages: NewInboxLeaf[], lastMessageL1BlockNumber: bigint): Promise { - return Promise.resolve(this.#messageStore.addNewL1ToL2Messages(messages, lastMessageL1BlockNumber)); + addL1ToL2Messages(messages: InboxLeaf[], lastMessageL1BlockNumber: bigint): Promise { + return Promise.resolve(this.#messageStore.addL1ToL2Messages(messages, lastMessageL1BlockNumber)); } /** - * Append new pending L1 to L2 messages to the store. - * @param messages - The L1 to L2 messages to be added to the store. - * @param l1BlockNumber - The L1 block number for which to add the messages. - * @returns True if the operation is successful. - */ - addPendingL1ToL2Messages(messages: L1ToL2Message[], l1BlockNumber: bigint): Promise { - return Promise.resolve(this.#messageStore.addPendingMessages(messages, l1BlockNumber)); - } - - /** - * Remove pending L1 to L2 messages from the store (if they were cancelled). - * @param messages - The entry keys to be removed from the store. - * @param l1BlockNumber - The L1 block number for which to remove the messages. - * @returns True if the operation is successful. - */ - cancelPendingL1ToL2EntryKeys(messages: Fr[], l1BlockNumber: bigint): Promise { - return Promise.resolve(this.#messageStore.cancelPendingMessages(messages, l1BlockNumber)); - } - - /** - * Messages that have been published in an L2 block are confirmed. - * Add them to the confirmed store, also remove them from the pending store. - * @param entryKeys - The entry keys to be removed from the store. - * @param blockNumber - The block for which to add the messages. - * @returns True if the operation is successful. + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - confirmL1ToL2EntryKeys(entryKeys: Fr[]): Promise { - return this.#messageStore.confirmPendingMessages(entryKeys); - } - - /** - * Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee - * @param limit - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). - * @returns The requested L1 to L2 entry keys. - */ - getPendingL1ToL2EntryKeys(limit: number): Promise { - return Promise.resolve(this.#messageStore.getPendingEntryKeysByFee(limit)); - } - - /** - * Gets the confirmed L1 to L2 message corresponding to the given entry key. - * @param entryKey - The entry key to look up. - * @returns The requested L1 to L2 message or throws if not found. - */ - getConfirmedL1ToL2Message(entryKey: Fr): Promise { - try { - return Promise.resolve(this.#messageStore.getConfirmedMessage(entryKey)); - } catch (err) { - return Promise.reject(err); - } + public getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise { + return Promise.resolve(this.#messageStore.getL1ToL2MessageIndex(l1ToL2Message)); } /** - * Gets new L1 to L2 message (to be) included in a given block. + * Gets L1 to L2 message (to be) included in a given block. * @param blockNumber - L2 block number to get messages for. * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). */ - getNewL1ToL2Messages(blockNumber: bigint): Promise { + getL1ToL2Messages(blockNumber: bigint): Promise { try { - return Promise.resolve(this.#messageStore.getNewL1ToL2Messages(blockNumber)); + return Promise.resolve(this.#messageStore.getL1ToL2Messages(blockNumber)); } catch (err) { return Promise.reject(err); } @@ -252,21 +207,19 @@ export class KVArchiverDataStore implements ArchiverDataStore { * Gets the number of the latest L2 block processed. * @returns The number of the latest L2 block processed. */ - getBlockNumber(): Promise { - return Promise.resolve(this.#blockStore.getBlockNumber()); + getSynchedL2BlockNumber(): Promise { + return Promise.resolve(this.#blockStore.getSynchedL2BlockNumber()); } /** * Gets the last L1 block number processed by the archiver */ - getL1BlockNumber(): Promise { - const addedBlock = this.#blockStore.getL1BlockNumber(); - const { addedMessages, cancelledMessages, newMessages } = this.#messageStore.getL1BlockNumber(); + getSynchedL1BlockNumbers(): Promise { + const blocks = this.#blockStore.getSynchedL1BlockNumber(); + const messages = this.#messageStore.getSynchedL1BlockNumber(); return Promise.resolve({ - addedBlock, - addedMessages, - newMessages, - cancelledMessages, + blocks, + messages, }); } } diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/message_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/message_store.ts index 1de475fbc8e..758f261c6ca 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/message_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/message_store.ts @@ -1,141 +1,65 @@ -import { L1ToL2Message, NewInboxLeaf } from '@aztec/circuit-types'; -import { Fr, L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/circuits.js'; +import { InboxLeaf } from '@aztec/circuit-types'; +import { + Fr, + INITIAL_L2_BLOCK_NUM, + L1_TO_L2_MSG_SUBTREE_HEIGHT, + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, +} from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; -import { AztecCounter, AztecKVStore, AztecMap, AztecSingleton } from '@aztec/kv-store'; - -/** - * A message stored in the database - */ -type Message = { - /** The L1ToL2Message */ - message: Buffer; - /** The message's fee */ - fee: number; - /** Has it _ever_ been confirmed? */ - confirmed: boolean; -}; +import { AztecKVStore, AztecMap, AztecSingleton } from '@aztec/kv-store'; /** * LMDB implementation of the ArchiverDataStore interface. */ export class MessageStore { - #newMessages: AztecMap; - #lastL1BlockNewMessages: AztecSingleton; - // TODO(#4492): Nuke the following when purging the old inbox - #pendingMessagesByFee: AztecCounter<[number, string]>; - #messages: AztecMap; - #lastL1BlockAddingMessages: AztecSingleton; - #lastL1BlockCancellingMessages: AztecSingleton; + #l1ToL2Messages: AztecMap; + #l1ToL2MessageIndices: AztecMap; + #lastL1BlockMessages: AztecSingleton; #log = createDebugLogger('aztec:archiver:message_store'); #l1ToL2MessagesSubtreeSize = 2 ** L1_TO_L2_MSG_SUBTREE_HEIGHT; constructor(private db: AztecKVStore) { - this.#newMessages = db.openMap('archiver_l1_to_l2_new_messages'); - this.#messages = db.openMap('archiver_l1_to_l2_messages'); - this.#pendingMessagesByFee = db.openCounter('archiver_messages_by_fee'); - this.#lastL1BlockNewMessages = db.openSingleton('archiver_last_l1_block_new_messages'); - this.#lastL1BlockAddingMessages = db.openSingleton('archiver_last_l1_block_adding_messages'); - this.#lastL1BlockCancellingMessages = db.openSingleton('archiver_last_l1_block_cancelling_messages'); + this.#l1ToL2Messages = db.openMap('archiver_l1_to_l2_messages'); + this.#l1ToL2MessageIndices = db.openMap('archiver_l1_to_l2_message_indices'); + this.#lastL1BlockMessages = db.openSingleton('archiver_last_l1_block_new_messages'); } /** - * Gets the last L1 block number that emitted new messages and the block that cancelled messages. + * Gets the last L1 block number that emitted new messages. * @returns The last L1 block number processed */ - getL1BlockNumber() { - return { - newMessages: this.#lastL1BlockNewMessages.get() ?? 0n, - // TODO(#4492): Nuke the following when purging the old inbox - addedMessages: this.#lastL1BlockAddingMessages.get() ?? 0n, - cancelledMessages: this.#lastL1BlockCancellingMessages.get() ?? 0n, - }; + getSynchedL1BlockNumber(): bigint { + return this.#lastL1BlockMessages.get() ?? 0n; } /** - * Append new L1 to L2 messages to the store. + * Append L1 to L2 messages to the store. * @param messages - The L1 to L2 messages to be added to the store. * @param lastMessageL1BlockNumber - The L1 block number in which the last message was emitted. * @returns True if the operation is successful. */ - addNewL1ToL2Messages(messages: NewInboxLeaf[], lastMessageL1BlockNumber: bigint): Promise { + addL1ToL2Messages(messages: InboxLeaf[], lastMessageL1BlockNumber: bigint): Promise { return this.db.transaction(() => { - const lastL1BlockNumber = this.#lastL1BlockNewMessages.get() ?? 0n; + const lastL1BlockNumber = this.#lastL1BlockMessages.get() ?? 0n; if (lastL1BlockNumber >= lastMessageL1BlockNumber) { return false; } - void this.#lastL1BlockNewMessages.set(lastMessageL1BlockNumber); + void this.#lastL1BlockMessages.set(lastMessageL1BlockNumber); for (const message of messages) { if (message.index >= this.#l1ToL2MessagesSubtreeSize) { throw new Error(`Message index ${message.index} out of subtree range`); } const key = `${message.blockNumber}-${message.index}`; - void this.#newMessages.setIfNotExists(key, message.leaf.toBuffer()); - } - - return true; - }); - } - - /** - * Append new pending L1 to L2 messages to the store. - * @param messages - The L1 to L2 messages to be added to the store. - * @param l1BlockNumber - The L1 block number for which to add the messages. - * @returns True if the operation is successful. - */ - addPendingMessages(messages: L1ToL2Message[], l1BlockNumber: bigint): Promise { - return this.db.transaction(() => { - const lastL1BlockNumber = this.#lastL1BlockAddingMessages.get() ?? 0n; - if (lastL1BlockNumber >= l1BlockNumber) { - return false; - } - - void this.#lastL1BlockAddingMessages.set(l1BlockNumber); - - for (const message of messages) { - const entryKey = message.entryKey?.toString(); - if (!entryKey) { - throw new Error('Message does not have an entry key'); - } - - void this.#messages.setIfNotExists(entryKey, { - message: message.toBuffer(), - fee: message.fee, - confirmed: false, - }); - - void this.#pendingMessagesByFee.update([message.fee, entryKey], 1); - } - - return true; - }); - } - - /** - * Remove pending L1 to L2 messages from the store (if they were cancelled). - * @param entryKeys - The entry keys to be removed from the store. - * @param l1BlockNumber - The L1 block number for which to remove the messages. - * @returns True if the operation is successful. - */ - cancelPendingMessages(entryKeys: Fr[], l1BlockNumber: bigint): Promise { - return this.db.transaction(() => { - const lastL1BlockNumber = this.#lastL1BlockCancellingMessages.get() ?? 0n; - if (lastL1BlockNumber >= l1BlockNumber) { - return false; - } - - void this.#lastL1BlockCancellingMessages.set(l1BlockNumber); + void this.#l1ToL2Messages.setIfNotExists(key, message.leaf.toBuffer()); - for (const entryKey of entryKeys) { - const messageCtx = this.#messages.get(entryKey.toString()); - if (!messageCtx) { - throw new Error(`Message ${entryKey.toString()} not found`); - } - - void this.#pendingMessagesByFee.update([messageCtx.fee, entryKey.toString()], -1); + const indexInTheWholeTree = + (message.blockNumber - BigInt(INITIAL_L2_BLOCK_NUM)) * BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP) + + message.index; + void this.#l1ToL2MessageIndices.setIfNotExists(message.leaf.toString(), indexInTheWholeTree); } return true; @@ -143,78 +67,25 @@ export class MessageStore { } /** - * Messages that have been published in an L2 block are confirmed. - * Add them to the confirmed store, also remove them from the pending store. - * @param entryKeys - The entry keys to be removed from the store. - * @returns True if the operation is successful. + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - confirmPendingMessages(entryKeys: Fr[]): Promise { - return this.db.transaction(() => { - for (const entryKey of entryKeys) { - if (entryKey.equals(Fr.ZERO)) { - continue; - } - - const messageCtx = this.#messages.get(entryKey.toString()); - if (!messageCtx) { - throw new Error(`Message ${entryKey.toString()} not found`); - } - messageCtx.confirmed = true; - - void this.#messages.set(entryKey.toString(), messageCtx); - void this.#pendingMessagesByFee.update([messageCtx.fee, entryKey.toString()], -1); - } - - return true; - }); - } - - /** - * Gets the confirmed L1 to L2 message corresponding to the given entry key. - * @param entryKey - The entry key to look up. - * @returns The requested L1 to L2 message or throws if not found. - */ - getConfirmedMessage(entryKey: Fr): L1ToL2Message { - const messageCtx = this.#messages.get(entryKey.toString()); - if (!messageCtx) { - throw new Error(`Message ${entryKey.toString()} not found`); + public getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise { + const index = this.#l1ToL2MessageIndices.get(l1ToL2Message.toString()); + if (index === undefined) { + throw new Error(`L1 to L2 message index not found in the store for message ${l1ToL2Message.toString()}`); } - - if (!messageCtx.confirmed) { - throw new Error(`Message ${entryKey.toString()} not confirmed`); - } - - return L1ToL2Message.fromBuffer(messageCtx.message); - } - - /** - * Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee - * @param limit - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). - * @returns The requested L1 to L2 entry keys. - */ - getPendingEntryKeysByFee(limit: number): Fr[] { - const entryKeys: Fr[] = []; - - for (const [[_, entryKey], count] of this.#pendingMessagesByFee.entries({ - reverse: true, - })) { - // put `count` copies of this message in the result list - entryKeys.push(...Array(count).fill(Fr.fromString(entryKey))); - if (entryKeys.length >= limit) { - break; - } - } - - return entryKeys; + return Promise.resolve(index); } - getNewL1ToL2Messages(blockNumber: bigint): Fr[] { + getL1ToL2Messages(blockNumber: bigint): Fr[] { const messages: Fr[] = []; let undefinedMessageFound = false; for (let messageIndex = 0; messageIndex < this.#l1ToL2MessagesSubtreeSize; messageIndex++) { // This is inefficient but probably fine for now. const key = `${blockNumber}-${messageIndex}`; - const message = this.#newMessages.get(key); + const message = this.#l1ToL2Messages.get(key); if (message) { if (undefinedMessageFound) { throw new Error(`L1 to L2 message gap found in block ${blockNumber}`); diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.test.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.test.ts index e21a76d1b49..1625483c0f3 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.test.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.test.ts @@ -1,98 +1,33 @@ -import { L1Actor, L1ToL2Message, L2Actor } from '@aztec/circuit-types'; +import { InboxLeaf } from '@aztec/circuit-types'; +import { INITIAL_L2_BLOCK_NUM, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; -import { L1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js'; +import { L1ToL2MessageStore } from './l1_to_l2_message_store.js'; describe('l1_to_l2_message_store', () => { let store: L1ToL2MessageStore; - let entryKey: Fr; - let msg: L1ToL2Message; beforeEach(() => { // already adds a message to the store store = new L1ToL2MessageStore(); - entryKey = Fr.random(); - msg = L1ToL2Message.random(); }); - it('addMessage adds a message', () => { - store.addMessage(entryKey, msg); - expect(store.getMessage(entryKey)).toEqual(msg); - }); - - it('addMessage increments the count if the message is already in the store', () => { - store.addMessage(entryKey, msg); - store.addMessage(entryKey, msg); - expect(store.getMessageAndCount(entryKey)).toEqual({ message: msg, count: 2 }); - }); -}); - -describe('pending_l1_to_l2_message_store', () => { - let store: PendingL1ToL2MessageStore; - let entryKey: Fr; - let msg: L1ToL2Message; - - beforeEach(() => { - // already adds a message to the store - store = new PendingL1ToL2MessageStore(); - entryKey = Fr.random(); - msg = L1ToL2Message.random(); - }); - - it('removeMessage removes the message if the count is 1', () => { - store.addMessage(entryKey, msg); - store.removeMessage(entryKey); - expect(store.getMessage(entryKey)).toBeUndefined(); - }); - - it("handles case when removing a message that doesn't exist", () => { - expect(() => store.removeMessage(new Fr(0))).not.toThrow(); - const one = new Fr(1); - expect(() => store.removeMessage(one)).toThrow(`Message with key ${one.value} not found in store`); - }); - - it('removeMessage decrements the count if the message is already in the store', () => { - store.addMessage(entryKey, msg); - store.addMessage(entryKey, msg); - store.addMessage(entryKey, msg); - store.removeMessage(entryKey); - expect(store.getMessageAndCount(entryKey)).toEqual({ message: msg, count: 2 }); - }); - - it('get messages for an empty store', () => { - expect(store.getEntryKeys(10)).toEqual([]); - }); - - it('getEntryKeys returns an empty array if limit is 0', () => { - store.addMessage(entryKey, msg); - expect(store.getEntryKeys(0)).toEqual([]); - }); - - it('get messages for a non-empty store when limit > number of messages in store', () => { - const entryKeys = [1, 2, 3, 4, 5].map(x => new Fr(x)); - entryKeys.forEach(entryKey => { - store.addMessage(entryKey, L1ToL2Message.random()); - }); - expect(store.getEntryKeys(10).length).toEqual(5); - }); - - it('get messages returns messages sorted by fees and also includes multiple of the same message', () => { - const entryKeys = [1, 2, 3, 3, 3, 4].map(x => new Fr(x)); - entryKeys.forEach(entryKey => { - // set msg.fee to entryKey to test the sort. - const msg = new L1ToL2Message( - L1Actor.random(), - L2Actor.random(), - Fr.random(), - Fr.random(), - 100, - Number(entryKey.value), - entryKey, - ); - store.addMessage(entryKey, msg); + it('adds a message and correctly returns its index', () => { + const blockNumber = 236n; + const msgs = Array.from({ length: 10 }, (_, i) => { + return new InboxLeaf(blockNumber, BigInt(i), Fr.random()); }); - const expectedMessageFees = [4n, 3n, 3n, 3n]; // the top 4. - const receivedMessageFees = store.getEntryKeys(4).map(key => key.value); - expect(receivedMessageFees).toEqual(expectedMessageFees); + for (const m of msgs) { + store.addMessage(m); + } + + const retrievedMsgs = store.getMessages(blockNumber); + expect(retrievedMsgs.length).toEqual(10); + + const msg = msgs[4]; + const index = store.getMessageIndex(msg.leaf); + expect(index).toEqual( + (blockNumber - BigInt(INITIAL_L2_BLOCK_NUM)) * BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP) + msg.index, + ); }); }); diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts index ae987fe9e4e..6cd0074c8d9 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts @@ -1,13 +1,15 @@ -import { L1ToL2Message, NewInboxLeaf } from '@aztec/circuit-types'; -import { L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/circuits.js/constants'; +import { InboxLeaf } from '@aztec/circuit-types'; +import { + INITIAL_L2_BLOCK_NUM, + L1_TO_L2_MSG_SUBTREE_HEIGHT, + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, +} from '@aztec/circuits.js/constants'; import { Fr } from '@aztec/foundation/fields'; /** - * A simple in-memory implementation of an L1 to L2 message store - * that handles message duplication. - * TODO(#4492): Clean this up + * A simple in-memory implementation of an L1 to L2 message store. */ -export class NewL1ToL2MessageStore { +export class L1ToL2MessageStore { /** * A map containing the entry key to the corresponding L1 to L2 * messages (and the number of times the message has been seen). @@ -18,7 +20,7 @@ export class NewL1ToL2MessageStore { constructor() {} - addMessage(message: NewInboxLeaf) { + addMessage(message: InboxLeaf) { if (message.index >= this.#l1ToL2MessagesSubtreeSize) { throw new Error(`Message index ${message.index} out of subtree range`); } @@ -46,93 +48,22 @@ export class NewL1ToL2MessageStore { } return messages; } -} -/** - * A simple in-memory implementation of an L1 to L2 message store - * that handles message duplication. - */ -export class L1ToL2MessageStore { /** - * A map containing the entry key to the corresponding L1 to L2 - * messages (and the number of times the message has been seen). + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - protected store: Map = new Map(); - - constructor() {} - - addMessage(entryKey: Fr, message: L1ToL2Message) { - const entryKeyBigInt = entryKey.toBigInt(); - const msgAndCount = this.store.get(entryKeyBigInt); - if (msgAndCount) { - msgAndCount.count++; - } else { - this.store.set(entryKeyBigInt, { message, count: 1 }); - } - } - - getMessage(entryKey: Fr): L1ToL2Message | undefined { - return this.store.get(entryKey.value)?.message; - } - - getMessageAndCount(entryKey: Fr): L1ToL2MessageAndCount | undefined { - return this.store.get(entryKey.value); - } -} - -/** - * Specifically for the store that will hold pending messages - * for removing messages or fetching multiple messages. - */ -export class PendingL1ToL2MessageStore extends L1ToL2MessageStore { - getEntryKeys(limit: number): Fr[] { - if (limit < 1) { - return []; - } - // fetch `limit` number of messages from the store with the highest fee. - // Note the store has multiple of the same message. So if a message has count 2, include both of them in the result: - const messages: Fr[] = []; - const sortedMessages = Array.from(this.store.values()).sort((a, b) => b.message.fee - a.message.fee); - for (const messageAndCount of sortedMessages) { - for (let i = 0; i < messageAndCount.count; i++) { - messages.push(messageAndCount.message.entryKey!); - if (messages.length === limit) { - return messages; - } + getMessageIndex(l1ToL2Message: Fr): bigint { + for (const [key, message] of this.store.entries()) { + if (message.equals(l1ToL2Message)) { + const [blockNumber, messageIndex] = key.split('-'); + const indexInTheWholeTree = + (BigInt(blockNumber) - BigInt(INITIAL_L2_BLOCK_NUM)) * BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP) + + BigInt(messageIndex); + return indexInTheWholeTree; } } - return messages; - } - - removeMessage(entryKey: Fr) { - // ignore 0 - entryKey is a hash, so a 0 can probabilistically never occur. It is best to skip it. - if (entryKey.equals(Fr.ZERO)) { - return; - } - - const entryKeyBigInt = entryKey.value; - const msgAndCount = this.store.get(entryKeyBigInt); - if (!msgAndCount) { - throw new Error(`Unable to remove message: L1 to L2 Message with key ${entryKeyBigInt} not found in store`); - } - if (msgAndCount.count > 1) { - msgAndCount.count--; - } else { - this.store.delete(entryKeyBigInt); - } + throw new Error(`L1 to L2 message index not found in the store for message ${l1ToL2Message.toString()}`); } } - -/** - * Useful to keep track of the number of times a message has been seen. - */ -type L1ToL2MessageAndCount = { - /** - * The message. - */ - message: L1ToL2Message; - /** - * The number of times the message has been seen. - */ - count: number; -}; 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 1c9a1d4d10c..657ea8679c0 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 @@ -2,26 +2,25 @@ import { Body, ExtendedUnencryptedL2Log, GetUnencryptedLogsResponse, - L1ToL2Message, + InboxLeaf, L2Block, L2BlockContext, L2BlockL2Logs, LogFilter, LogId, LogType, - NewInboxLeaf, TxEffect, TxHash, TxReceipt, TxStatus, UnencryptedL2Log, } from '@aztec/circuit-types'; -import { Fr, INITIAL_L2_BLOCK_NUM, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js'; +import { Fr, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; -import { ArchiverDataStore } from '../archiver_store.js'; -import { L1ToL2MessageStore, NewL1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js'; +import { ArchiverDataStore, ArchiverL1SynchPoint } from '../archiver_store.js'; +import { L1ToL2MessageStore } from './l1_to_l2_message_store.js'; /** * Simple, in-memory implementation of an archiver data store. @@ -54,27 +53,16 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ private unencryptedLogsPerBlock: L2BlockL2Logs[] = []; - // TODO(#4492): Nuke the other message stores - private newL1ToL2Messages = new NewL1ToL2MessageStore(); - /** - * Contains all the confirmed L1 to L2 messages (i.e. messages that were consumed in an L2 block) - * It is a map of entryKey to the corresponding L1 to L2 message and the number of times it has appeared + * Contains all L1 to L2 messages. */ - private confirmedL1ToL2Messages: L1ToL2MessageStore = new L1ToL2MessageStore(); - - /** - * Contains all the pending L1 to L2 messages (accounts for duplication of messages) - */ - private pendingL1ToL2Messages: PendingL1ToL2MessageStore = new PendingL1ToL2MessageStore(); + private l1ToL2Messages = new L1ToL2MessageStore(); private contractClasses: Map = new Map(); private contractInstances: Map = new Map(); private lastL1BlockNewMessages: bigint = 0n; - private lastL1BlockAddedMessages: bigint = 0n; - private lastL1BlockCancelledMessages: bigint = 0n; constructor( /** The max number of logs that can be obtained in 1 "getUnencryptedLogs" call. */ @@ -167,75 +155,30 @@ export class MemoryArchiverStore implements ArchiverDataStore { } /** - * Append new L1 to L2 messages to the store. + * Append L1 to L2 messages to the store. * @param messages - The L1 to L2 messages to be added to the store. * @param lastMessageL1BlockNumber - The L1 block number in which the last message was emitted. * @returns True if the operation is successful. */ - public addNewL1ToL2Messages(messages: NewInboxLeaf[], lastMessageL1BlockNumber: bigint): Promise { + public addL1ToL2Messages(messages: InboxLeaf[], lastMessageL1BlockNumber: bigint): Promise { if (lastMessageL1BlockNumber <= this.lastL1BlockNewMessages) { return Promise.resolve(false); } this.lastL1BlockNewMessages = lastMessageL1BlockNumber; for (const message of messages) { - this.newL1ToL2Messages.addMessage(message); - } - return Promise.resolve(true); - } - - /** - * Append new pending L1 to L2 messages to the store. - * @param messages - The L1 to L2 messages to be added to the store. - * @param l1BlockNumber - The L1 block number for which to add the messages. - * @returns True if the operation is successful (always in this implementation). - */ - public addPendingL1ToL2Messages(messages: L1ToL2Message[], l1BlockNumber: bigint): Promise { - if (l1BlockNumber <= this.lastL1BlockAddedMessages) { - return Promise.resolve(false); - } - - this.lastL1BlockAddedMessages = l1BlockNumber; - for (const message of messages) { - this.pendingL1ToL2Messages.addMessage(message.entryKey!, message); - } - return Promise.resolve(true); - } - - /** - * Remove pending L1 to L2 messages from the store (if they were cancelled). - * @param messages - The entry keys to be removed from the store. - * @param l1BlockNumber - The L1 block number for which to remove the messages. - * @returns True if the operation is successful (always in this implementation). - */ - public cancelPendingL1ToL2EntryKeys(messages: Fr[], l1BlockNumber: bigint): Promise { - if (l1BlockNumber <= this.lastL1BlockCancelledMessages) { - return Promise.resolve(false); + this.l1ToL2Messages.addMessage(message); } - - this.lastL1BlockCancelledMessages = l1BlockNumber; - messages.forEach(entryKey => { - this.pendingL1ToL2Messages.removeMessage(entryKey); - }); return Promise.resolve(true); } /** - * Messages that have been published in an L2 block are confirmed. - * Add them to the confirmed store, also remove them from the pending store. - * @param entryKeys - The entry keys to be removed from the store. - * @returns True if the operation is successful (always in this implementation). + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - public confirmL1ToL2EntryKeys(entryKeys: Fr[]): Promise { - entryKeys.forEach(entryKey => { - if (entryKey.equals(Fr.ZERO)) { - return; - } - - this.confirmedL1ToL2Messages.addMessage(entryKey, this.pendingL1ToL2Messages.getMessage(entryKey)!); - this.pendingL1ToL2Messages.removeMessage(entryKey); - }); - return Promise.resolve(true); + public getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise { + return Promise.resolve(this.l1ToL2Messages.getMessageIndex(l1ToL2Message)); } /** @@ -289,34 +232,12 @@ export class MemoryArchiverStore implements ArchiverDataStore { } /** - * Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee - * @param limit - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). - * @returns The requested L1 to L2 entry keys. - */ - public getPendingL1ToL2EntryKeys(limit: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP): Promise { - return Promise.resolve(this.pendingL1ToL2Messages.getEntryKeys(limit)); - } - - /** - * Gets the confirmed L1 to L2 message corresponding to the given entry key. - * @param entryKey - The entry key to look up. - * @returns The requested L1 to L2 message or throws if not found. - */ - public getConfirmedL1ToL2Message(entryKey: Fr): Promise { - const message = this.confirmedL1ToL2Messages.getMessage(entryKey); - if (!message) { - throw new Error(`L1 to L2 Message with key ${entryKey.toString()} not found in the confirmed messages store`); - } - return Promise.resolve(message); - } - - /** - * Gets new L1 to L2 message (to be) included in a given block. + * Gets L1 to L2 message (to be) included in a given block. * @param blockNumber - L2 block number to get messages for. * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). */ - getNewL1ToL2Messages(blockNumber: bigint): Promise { - return Promise.resolve(this.newL1ToL2Messages.getMessages(blockNumber)); + getL1ToL2Messages(blockNumber: bigint): Promise { + return Promise.resolve(this.l1ToL2Messages.getMessages(blockNumber)); } /** @@ -427,24 +348,20 @@ export class MemoryArchiverStore implements ArchiverDataStore { * Gets the number of the latest L2 block processed. * @returns The number of the latest L2 block processed. */ - public getBlockNumber(): Promise { + public getSynchedL2BlockNumber(): Promise { if (this.l2BlockContexts.length === 0) { return Promise.resolve(INITIAL_L2_BLOCK_NUM - 1); } return Promise.resolve(this.l2BlockContexts[this.l2BlockContexts.length - 1].block.number); } - public getL1BlockNumber() { - const addedBlock = this.l2BlockContexts[this.l2BlockContexts.length - 1]?.block?.getL1BlockNumber() ?? 0n; - const newMessages = this.lastL1BlockNewMessages; - const addedMessages = this.lastL1BlockAddedMessages; - const cancelledMessages = this.lastL1BlockCancelledMessages; + public getSynchedL1BlockNumbers(): Promise { + const blocks = this.l2BlockContexts[this.l2BlockContexts.length - 1]?.block?.getL1BlockNumber() ?? 0n; + const messages = this.lastL1BlockNewMessages; return Promise.resolve({ - addedBlock, - newMessages, - addedMessages, - cancelledMessages, + blocks, + messages, }); } } diff --git a/yarn-project/archiver/src/index.ts b/yarn-project/archiver/src/index.ts index a2d04eccd4e..fb3f8da310a 100644 --- a/yarn-project/archiver/src/index.ts +++ b/yarn-project/archiver/src/index.ts @@ -1,9 +1,7 @@ -import { EthAddress } from '@aztec/foundation/eth-address'; import { createDebugLogger } from '@aztec/foundation/log'; import { fileURLToPath } from '@aztec/foundation/url'; -import { RollupAbi } from '@aztec/l1-artifacts'; -import { createPublicClient, getAddress, getContract, http } from 'viem'; +import { createPublicClient, http } from 'viem'; import { localhost } from 'viem/chains'; import { Archiver, getConfigEnvVars } from './archiver/index.js'; @@ -29,23 +27,11 @@ async function main() { const archiverStore = new MemoryArchiverStore(1000); - // TODO(#4492): Nuke this once the old inbox is purged - let newInboxAddress!: EthAddress; - { - const rollup = getContract({ - address: getAddress(l1Contracts.rollupAddress.toString()), - abi: RollupAbi, - client: publicClient, - }); - newInboxAddress = EthAddress.fromString(await rollup.read.NEW_INBOX()); - } - const archiver = new Archiver( publicClient, l1Contracts.rollupAddress, l1Contracts.availabilityOracleAddress, l1Contracts.inboxAddress, - newInboxAddress, l1Contracts.registryAddress, archiverStore, ); diff --git a/yarn-project/archiver/src/rpc/archiver_client.ts b/yarn-project/archiver/src/rpc/archiver_client.ts index 0fc7edba5cf..a77b7df3d6e 100644 --- a/yarn-project/archiver/src/rpc/archiver_client.ts +++ b/yarn-project/archiver/src/rpc/archiver_client.ts @@ -1,6 +1,5 @@ import { ExtendedUnencryptedL2Log, - L1ToL2Message, L2Block, L2BlockL2Logs, NullifierMembershipWitness, @@ -18,7 +17,6 @@ export const createArchiverClient = (url: string, fetch = makeFetch([1, 2, 3], t EthAddress, ExtendedUnencryptedL2Log, Fr, - L1ToL2Message, L2Block, L2BlockL2Logs, }, diff --git a/yarn-project/archiver/src/rpc/archiver_server.ts b/yarn-project/archiver/src/rpc/archiver_server.ts index 672da4d2f6a..6a9efa8d1fe 100644 --- a/yarn-project/archiver/src/rpc/archiver_server.ts +++ b/yarn-project/archiver/src/rpc/archiver_server.ts @@ -1,6 +1,5 @@ import { ExtendedUnencryptedL2Log, - L1ToL2Message, L2Block, L2BlockL2Logs, NullifierMembershipWitness, @@ -24,7 +23,6 @@ export function createArchiverRpcServer(archiverService: Archiver): JsonRpcServe EthAddress, ExtendedUnencryptedL2Log, Fr, - L1ToL2Message, L2Block, L2BlockL2Logs, TxEffect, diff --git a/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts b/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts index 9c3c698b77d..24cea812955 100644 --- a/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts +++ b/yarn-project/aztec-node/src/aztec-node/http_rpc_server.ts @@ -1,7 +1,6 @@ import { AztecNode, ExtendedUnencryptedL2Log, - L1ToL2MessageAndIndex, L2Block, L2BlockL2Logs, LogId, @@ -38,7 +37,6 @@ export function createAztecNodeRpcServer(node: AztecNode) { LogId, TxHash, SiblingPath, - L1ToL2MessageAndIndex, }, { Tx, TxReceipt, L2BlockL2Logs, NullifierMembershipWitness }, // disable methods not part of the AztecNode interface diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 2a5f1823d5b..cc851ee4258 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -1,8 +1,8 @@ import { ArchiveSource, Archiver, KVArchiverDataStore, createArchiverClient } from '@aztec/archiver'; import { AztecNode, + BlockNumber, GetUnencryptedLogsResponse, - L1ToL2MessageAndIndex, L1ToL2MessageSource, L2Block, L2BlockL2Logs, @@ -364,30 +364,23 @@ export class AztecNodeService implements AztecNode { } /** - * Gets a confirmed/consumed L1 to L2 message for the given entry key - * and its index in the merkle tree. - * @param entryKey - The entry key. - * @returns The map containing the message and index. - */ - public async getL1ToL2MessageAndIndex(entryKey: Fr): Promise { - // todo: #697 - make this one lookup. - const index = (await this.findLeafIndex('latest', MerkleTreeId.L1_TO_L2_MESSAGE_TREE, entryKey))!; - const message = await this.l1ToL2MessageSource.getConfirmedL1ToL2Message(entryKey); - return Promise.resolve(new L1ToL2MessageAndIndex(index, message)); - } - - /** - * Returns a sibling path for a leaf in the committed l1 to l2 data tree. + * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree. * @param blockNumber - The block number at which to get the data. - * @param leafIndex - Index of the leaf in the tree. - * @returns The sibling path. + * @param l1ToL2Message - The l1ToL2Message to get the index / sibling path for. + * @throws If the message is not found. + * @returns A tuple of the index and the sibling path of the L1ToL2Message. */ - public async getL1ToL2MessageSiblingPath( - blockNumber: number | 'latest', - leafIndex: bigint, - ): Promise> { + public async getL1ToL2MessageIndexAndSiblingPath( + blockNumber: BlockNumber, + l1ToL2Message: Fr, + ): Promise<[bigint, SiblingPath]> { + const index = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message); const committedDb = await this.#getWorldState(blockNumber); - return committedDb.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, leafIndex); + const siblingPath = await committedDb.getSiblingPath( + MerkleTreeId.L1_TO_L2_MESSAGE_TREE, + index, + ); + return [index, siblingPath]; } /** diff --git a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts index e6a3bde29a9..30a70f88fa8 100644 --- a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts +++ b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts @@ -7,7 +7,6 @@ import { createJsonRpcClient, defaultFetch } from '@aztec/foundation/json-rpc/cl import { AztecNode } from '../../interfaces/aztec-node.js'; import { NullifierMembershipWitness } from '../../interfaces/nullifier_tree.js'; -import { L1ToL2MessageAndIndex } from '../../l1_to_l2_message.js'; import { L2Block } from '../../l2_block.js'; import { ExtendedUnencryptedL2Log, L2BlockL2Logs, LogId } from '../../logs/index.js'; import { SiblingPath } from '../../sibling_path/index.js'; @@ -36,7 +35,6 @@ export function createAztecNodeClient(url: string, fetch = defaultFetch): AztecN LogId, TxHash, SiblingPath, - L1ToL2MessageAndIndex, }, { Tx, TxReceipt, L2BlockL2Logs, NullifierMembershipWitness }, false, diff --git a/yarn-project/circuit-types/src/body.ts b/yarn-project/circuit-types/src/body.ts index b77c2160a17..4e6eb00e5f0 100644 --- a/yarn-project/circuit-types/src/body.ts +++ b/yarn-project/circuit-types/src/body.ts @@ -104,11 +104,11 @@ export class Body { numUnencryptedLogsPerCall = 1, numL1ToL2MessagesPerCall = 2, ) { - const newL1ToL2Messages = makeTuple(numL1ToL2MessagesPerCall, Fr.random); + const l1ToL2Messages = makeTuple(numL1ToL2MessagesPerCall, Fr.random); const txEffects = [...new Array(txsPerBlock)].map(_ => TxEffect.random(numPrivateCallsPerTx, numPublicCallsPerTx, numEncryptedLogsPerCall, numUnencryptedLogsPerCall), ); - return new Body(padArrayEnd(newL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP), txEffects); + return new Body(padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP), txEffects); } } diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index f1ccd6ff13d..dffadd90541 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -11,7 +11,6 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; -import { L1ToL2MessageAndIndex } from '../l1_to_l2_message.js'; import { L2Block } from '../l2_block.js'; import { GetUnencryptedLogsResponse, L2BlockL2Logs, LogFilter, LogType } from '../logs/index.js'; import { MerkleTreeId } from '../merkle_tree_id.js'; @@ -23,7 +22,7 @@ import { NullifierMembershipWitness } from './nullifier_tree.js'; import { PublicDataWitness } from './public_data_tree.js'; /** Helper type for a specific L2 block number or the latest block number */ -type BlockNumber = number | 'latest'; +export type BlockNumber = number | 'latest'; /** * The aztec node. @@ -62,23 +61,16 @@ export interface AztecNode { ): Promise>; /** - * Gets a confirmed/consumed L1 to L2 message for the given entry key (throws if not found). - * and its index in the merkle tree - * @param entryKey - The entry key. - * @returns The map containing the message and index. - */ - getL1ToL2MessageAndIndex(entryKey: Fr): Promise; - - /** - * Returns a sibling path for a leaf in the committed l1 to l2 data tree. + * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree. * @param blockNumber - The block number at which to get the data. - * @param leafIndex - Index of the leaf in the tree. - * @returns The sibling path. + * @param l1ToL2Message - The l1ToL2Message to get the index / sibling path for. + * @throws If the message is not found. + * @returns A tuple of the index and the sibling path of the message. */ - getL1ToL2MessageSiblingPath( + getL1ToL2MessageIndexAndSiblingPath( blockNumber: BlockNumber, - leafIndex: bigint, - ): Promise>; + l1ToL2Message: Fr, + ): Promise<[bigint, SiblingPath]>; /** * Returns the index of a l2ToL1Message in a ephemeral l2 to l1 data tree as well as its sibling path. @@ -90,7 +82,7 @@ export interface AztecNode { * @returns A tuple of the index and the sibling path of the L2ToL1Message. */ getL2ToL1MessageIndexAndSiblingPath( - blockNumber: number | 'latest', + blockNumber: BlockNumber, l2ToL1Message: Fr, ): Promise<[number, SiblingPath]>; diff --git a/yarn-project/circuit-types/src/l1_to_l2_message.test.ts b/yarn-project/circuit-types/src/l1_to_l2_message.test.ts index 1eb0f6f948a..842d0b5428f 100644 --- a/yarn-project/circuit-types/src/l1_to_l2_message.test.ts +++ b/yarn-project/circuit-types/src/l1_to_l2_message.test.ts @@ -1,6 +1,4 @@ -import { randomInt } from '@aztec/foundation/crypto'; - -import { L1ToL2Message, L1ToL2MessageAndIndex } from './l1_to_l2_message.js'; +import { L1ToL2Message } from './l1_to_l2_message.js'; describe('L1 to L2 message', () => { it('can encode an L1 to L2 message to buffer and back', () => { @@ -9,26 +7,4 @@ describe('L1 to L2 message', () => { const recovered = L1ToL2Message.fromBuffer(buffer); expect(recovered).toEqual(msg); }); - - it('can encode an L1ToL2MessageAndIndex to buffer and back', () => { - const index = BigInt(randomInt(1000)); // Generate a random BigInt - const msg = L1ToL2Message.random(); - const l1ToL2MsgAndIndex = new L1ToL2MessageAndIndex(index, msg); - - const buffer = l1ToL2MsgAndIndex.toBuffer(); - const recovered = L1ToL2MessageAndIndex.fromBuffer(buffer); - - expect(recovered).toEqual(l1ToL2MsgAndIndex); - }); - - it('can encode an L1ToL2MessageAndIndex to string and back', () => { - const index = BigInt(randomInt(1000)); // Generate a random BigInt - const msg = L1ToL2Message.random(); - const l1ToL2MsgAndIndex = new L1ToL2MessageAndIndex(index, msg); - - const stringData = l1ToL2MsgAndIndex.toString(); - const recovered = L1ToL2MessageAndIndex.fromString(stringData); - - expect(recovered).toEqual(l1ToL2MsgAndIndex); - }); }); diff --git a/yarn-project/circuit-types/src/l1_to_l2_message.ts b/yarn-project/circuit-types/src/l1_to_l2_message.ts index af3854998e5..eabc17cc934 100644 --- a/yarn-project/circuit-types/src/l1_to_l2_message.ts +++ b/yarn-project/circuit-types/src/l1_to_l2_message.ts @@ -1,5 +1,6 @@ +// TODO(#5264) Separate classes here to individual files, rename InboxLeaf to something less ugly and check usage of L1ToL2Message. import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; +import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; import { randomInt, sha256 } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -14,22 +15,14 @@ export interface L1ToL2MessageSource { * @param blockNumber - L2 block number to get messages for. * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found). */ - getNewL1ToL2Messages(blockNumber: bigint): Promise; + getL1ToL2Messages(blockNumber: bigint): Promise; /** - * Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee - * @param limit - The maximum number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). - * @returns The requested L1 to L2 messages' keys. + * Gets the L1 to L2 message index in the L1 to L2 message tree. + * @param l1ToL2Message - The L1 to L2 message. + * @returns The index of the L1 to L2 message in the L1 to L2 message tree. */ - getPendingL1ToL2EntryKeys(limit?: number): Promise; - - /** - * Gets the confirmed L1 to L2 message with the given entry key. - * i.e. message that has already been consumed by the sequencer and published in an L2 Block - * @param entryKey - The entry key. - * @returns The confirmed L1 to L2 message (throws if not found) - */ - getConfirmedL1ToL2Message(entryKey: Fr): Promise; + getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise; /** * Gets the number of the latest L2 block processed by the implementation. @@ -38,7 +31,7 @@ export interface L1ToL2MessageSource { getBlockNumber(): Promise; } -export class NewInboxLeaf { +export class InboxLeaf { constructor( /** L2 block number in which the message will be included. */ public readonly blockNumber: bigint, @@ -52,51 +45,17 @@ export class NewInboxLeaf { return serializeToBuffer([this.blockNumber, this.index, this.leaf]); } - fromBuffer(buffer: Buffer | BufferReader): NewInboxLeaf { + fromBuffer(buffer: Buffer | BufferReader): InboxLeaf { const reader = BufferReader.asReader(buffer); const blockNumber = toBigIntBE(reader.readBytes(32)); const index = toBigIntBE(reader.readBytes(32)); const leaf = reader.readObject(Fr); - return new NewInboxLeaf(blockNumber, index, leaf); - } -} - -/** - * L1AndL2Message and Index (in the merkle tree) as one type - * TODO(#4492): Nuke the following when purging the old inbox - */ -export class L1ToL2MessageAndIndex { - constructor( - /** the index in the L1 to L2 Message tree. */ - public readonly index: bigint, - /** The message. */ - public readonly message: L1ToL2Message, - ) {} - - toBuffer(): Buffer { - return Buffer.concat([toBufferBE(this.index, 32), this.message.toBuffer()]); - } - - toString(): string { - return this.toBuffer().toString('hex'); - } - - static fromString(data: string): L1ToL2MessageAndIndex { - const buffer = Buffer.from(data, 'hex'); - return L1ToL2MessageAndIndex.fromBuffer(buffer); - } - - static fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - const index = toBigIntBE(reader.readBytes(32)); - const message = L1ToL2Message.fromBuffer(reader); - return new L1ToL2MessageAndIndex(index, message); + return new InboxLeaf(blockNumber, index, leaf); } } /** * The format of an L1 to L2 Message. - * TODO(#4492): Nuke the following when purging the old inbox */ export class L1ToL2Message { constructor( @@ -116,14 +75,6 @@ export class L1ToL2Message { * The hash of the spending secret. */ public readonly secretHash: Fr, - /** - * The deadline for the message. - */ - public readonly deadline: number, - /** - * The fee for the message. - */ - public readonly fee: number, /** * The entry key for the message - optional. */ @@ -135,18 +86,11 @@ export class L1ToL2Message { * @returns The message as an array of fields (in order). */ toFields(): Fr[] { - return [ - ...this.sender.toFields(), - ...this.recipient.toFields(), - this.content, - this.secretHash, - new Fr(BigInt(this.deadline)), - new Fr(BigInt(this.fee)), - ]; + return [...this.sender.toFields(), ...this.recipient.toFields(), this.content, this.secretHash]; } toBuffer(): Buffer { - return serializeToBuffer(this.sender, this.recipient, this.content, this.secretHash, this.deadline, this.fee); + return serializeToBuffer(this.sender, this.recipient, this.content, this.secretHash); } hash(): Fr { @@ -159,9 +103,7 @@ export class L1ToL2Message { const recipient = reader.readObject(L2Actor); const content = Fr.fromBuffer(reader); const secretHash = Fr.fromBuffer(reader); - const deadline = reader.readNumber(); - const fee = reader.readNumber(); - return new L1ToL2Message(sender, recipient, content, secretHash, deadline, fee); + return new L1ToL2Message(sender, recipient, content, secretHash); } toString(): string { @@ -174,25 +116,16 @@ export class L1ToL2Message { } static empty(): L1ToL2Message { - return new L1ToL2Message(L1Actor.empty(), L2Actor.empty(), Fr.ZERO, Fr.ZERO, 0, 0); + return new L1ToL2Message(L1Actor.empty(), L2Actor.empty(), Fr.ZERO, Fr.ZERO); } static random(entryKey?: Fr): L1ToL2Message { - return new L1ToL2Message( - L1Actor.random(), - L2Actor.random(), - Fr.random(), - Fr.random(), - randomInt(1000), - randomInt(1000), - entryKey, - ); + return new L1ToL2Message(L1Actor.random(), L2Actor.random(), Fr.random(), Fr.random(), entryKey); } } /** * The sender of an L1 to L2 message. - * TODO(#4492): Move to separate file when purging the old inbox */ export class L1Actor { constructor( @@ -232,7 +165,6 @@ export class L1Actor { /** * The recipient of an L2 message. - * TODO(#4492): Move to separate file when purging the old inbox */ export class L2Actor { constructor( diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 32b1ba18344..7a355ba42fc 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -92,7 +92,7 @@ export const FUNCTION_DATA_LENGTH = 2; export const FUNCTION_LEAF_PREIMAGE_LENGTH = 5; export const GLOBAL_VARIABLES_LENGTH = 6; export const HEADER_LENGTH = 23; -export const L1_TO_L2_MESSAGE_LENGTH = 8; +export const L1_TO_L2_MESSAGE_LENGTH = 6; export const L2_TO_L1_MESSAGE_LENGTH = 2; export const NULLIFIER_KEY_VALIDATION_REQUEST_LENGTH = 4; export const NULLIFIER_KEY_VALIDATION_REQUEST_CONTEXT_LENGTH = 5; diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts index ad81de5cea7..00ba5b5bb1c 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts @@ -10,11 +10,13 @@ import { TxStatus, computeAuthWitMessageHash, } from '@aztec/aztec.js'; -import { keccak, sha256 } from '@aztec/foundation/crypto'; +import { sha256 } from '@aztec/foundation/crypto'; import { serializeToBuffer } from '@aztec/foundation/serialize'; import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts.js'; -import { delay, setup } from './fixtures/utils.js'; +import { toFunctionSelector } from 'viem/utils'; + +import { setup } from './fixtures/utils.js'; import { CrossChainTestHarness } from './shared/cross_chain_test_harness.js'; describe('e2e_cross_chain_messaging', () => { @@ -32,9 +34,10 @@ describe('e2e_cross_chain_messaging', () => { let outbox: any; beforeEach(async () => { - const { pxe, deployL1ContractsValues, wallets, logger: logger_, teardown: teardown_ } = await setup(2); + const { aztecNode, pxe, deployL1ContractsValues, wallets, logger: logger_, teardown: teardown_ } = await setup(2); crossChainTestHarness = await CrossChainTestHarness.new( + aztecNode, pxe, deployL1ContractsValues.publicClient, deployL1ContractsValues.walletClient, @@ -72,24 +75,17 @@ describe('e2e_cross_chain_messaging', () => { await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); // 2. Deposit tokens to the TokenPortal - const entryKeyInbox = await crossChainTestHarness.sendTokensToPortalPrivate( + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPrivate( secretHashForRedeemingMintedNotes, bridgeAmount, secretHashForL2MessageConsumption, ); expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); - expect(await crossChainTestHarness.inbox.read.contains([entryKeyInbox.toString()])).toBeTruthy(); - - // Wait for the archiver to process the message - await delay(5000); /// waiting 5 seconds. - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - const unrelatedMintAmount = 99n; - await crossChainTestHarness.mintTokensPublicOnL2(unrelatedMintAmount); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, unrelatedMintAmount); + await crossChainTestHarness.makeMessageConsumable(msgLeaf); - // 3. Consume L1-> L2 message and mint private tokens on L2 - await crossChainTestHarness.consumeMessageOnAztecAndMintSecretly( + // 3. Consume L1 -> L2 message and mint private tokens on L2 + await crossChainTestHarness.consumeMessageOnAztecAndMintPrivately( secretHashForRedeemingMintedNotes, bridgeAmount, secretForL2MessageConsumption, @@ -137,28 +133,22 @@ describe('e2e_cross_chain_messaging', () => { crossChainTestHarness.generateClaimSecret(); await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); - const entryKeyInbox = await crossChainTestHarness.sendTokensToPortalPrivate( + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPrivate( secretHashForRedeemingMintedNotes, bridgeAmount, secretHashForL2MessageConsumption, ); expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); - expect(await crossChainTestHarness.inbox.read.contains([entryKeyInbox.toString()])).toBeTruthy(); - // Wait for the archiver to process the message - await delay(5000); /// waiting 5 seconds. + // Wait for the message to be available for consumption + await crossChainTestHarness.makeMessageConsumable(msgLeaf); - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - const unrelatedMintAmount = 99n; - await crossChainTestHarness.mintTokensPublicOnL2(unrelatedMintAmount); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, unrelatedMintAmount); - - // 3. Consume L1-> L2 message and mint private tokens on L2 + // 3. Consume L1 -> L2 message and mint private tokens on L2 const content = Fr.fromBufferReduce( sha256( Buffer.concat([ - keccak(Buffer.from('mint_private(bytes32,uint256,address)')).subarray(0, 4), - serializeToBuffer(...[secretHashForL2MessageConsumption, new Fr(bridgeAmount), ethAccount.toBuffer32()]), + Buffer.from(toFunctionSelector('mint_private(bytes32,uint256)').substring(2), 'hex'), + serializeToBuffer(...[secretHashForL2MessageConsumption, new Fr(bridgeAmount)]), ]), ), ); @@ -167,27 +157,20 @@ describe('e2e_cross_chain_messaging', () => { new L2Actor(l2Bridge.address, 1), content, secretHashForL2MessageConsumption, - 2 ** 32 - 1, - 0, ); // Sending wrong secret hashes should fail: await expect( l2Bridge .withWallet(user2Wallet) - .methods.claim_private( - secretHashForL2MessageConsumption, - bridgeAmount, - ethAccount, - secretForL2MessageConsumption, - ) + .methods.claim_private(secretHashForL2MessageConsumption, bridgeAmount, secretForL2MessageConsumption) .simulate(), - ).rejects.toThrowError(`Message ${wrongMessage.hash().toString()} not found`); + ).rejects.toThrow(`L1 to L2 message index not found in the store for message ${wrongMessage.hash().toString()}`); // send the right one - const consumptionTx = l2Bridge .withWallet(user2Wallet) - .methods.claim_private(secretHashForRedeemingMintedNotes, bridgeAmount, ethAccount, secretForL2MessageConsumption) + .methods.claim_private(secretHashForRedeemingMintedNotes, bridgeAmount, secretForL2MessageConsumption) .send(); const consumptionReceipt = await consumptionTx.wait(); expect(consumptionReceipt.status).toBe(TxStatus.MINED); @@ -218,7 +201,7 @@ describe('e2e_cross_chain_messaging', () => { .withWallet(user1Wallet) .methods.exit_to_l1_private(l2Token.address, ethAccount, withdrawAmount, EthAddress.ZERO, nonce) .simulate(), - ).rejects.toThrowError(`Unknown auth witness for message hash ${expectedBurnMessageHash.toString()}`); + ).rejects.toThrow(`Unknown auth witness for message hash ${expectedBurnMessageHash.toString()}`); }, 120_000); it("Can't claim funds publicly if they were deposited privately", async () => { @@ -230,25 +213,21 @@ describe('e2e_cross_chain_messaging', () => { const [secretForL2MessageConsumption, secretHashForL2MessageConsumption] = crossChainTestHarness.generateClaimSecret(); - const entryKey = await crossChainTestHarness.sendTokensToPortalPrivate( + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPrivate( Fr.random(), bridgeAmount, secretHashForL2MessageConsumption, ); expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(0n); - expect(await crossChainTestHarness.inbox.read.contains([entryKey.toString()])).toBeTruthy(); - - // Wait for the archiver to process the message - await delay(5000); /// waiting 5 seconds. - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - await crossChainTestHarness.mintTokensPublicOnL2(0n); + // Wait for the message to be available for consumption + await crossChainTestHarness.makeMessageConsumable(msgLeaf); const content = Fr.fromBufferReduce( sha256( Buffer.concat([ - keccak(Buffer.from('mint_public(bytes32,uint256,address)')).subarray(0, 4), - serializeToBuffer(...[ownerAddress, new Fr(bridgeAmount), ethAccount.toBuffer32()]), + Buffer.from(toFunctionSelector('mint_public(bytes32,uint256)').substring(2), 'hex'), + serializeToBuffer(...[ownerAddress, new Fr(bridgeAmount)]), ]), ), ); @@ -257,16 +236,14 @@ describe('e2e_cross_chain_messaging', () => { new L2Actor(l2Bridge.address, 1), content, secretHashForL2MessageConsumption, - 2 ** 32 - 1, - 0, ); - // 3. Consume L1-> L2 message and try to mint publicly on L2 - should fail + // 3. Consume L1 -> L2 message and try to mint publicly on L2 - should fail await expect( l2Bridge .withWallet(user2Wallet) - .methods.claim_public(ownerAddress, bridgeAmount, ethAccount, secretForL2MessageConsumption) + .methods.claim_public(ownerAddress, bridgeAmount, secretForL2MessageConsumption) .simulate(), - ).rejects.toThrowError(`Message ${wrongMessage.hash().toString()} not found`); + ).rejects.toThrow(`Message ${wrongMessage.hash().toString()} not found`); }, 120_000); }); diff --git a/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts b/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts index b6da1029eb0..e9d7363e599 100644 --- a/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts @@ -1,6 +1,7 @@ import { AccountWallet, AztecAddress, + AztecNode, CompleteAddress, DebugLogger, DeployL1Contracts, @@ -10,12 +11,10 @@ import { L1ToL2Message, L2Actor, PXE, - TxStatus, computeAuthWitMessageHash, computeMessageSecretHash, - sleep, } from '@aztec/aztec.js'; -import { keccak, sha256 } from '@aztec/foundation/crypto'; +import { sha256 } from '@aztec/foundation/crypto'; import { serializeToBuffer } from '@aztec/foundation/serialize'; import { InboxAbi, OutboxAbi } from '@aztec/l1-artifacts'; import { TestContract } from '@aztec/noir-contracts.js'; @@ -23,12 +22,13 @@ import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge'; import { Hex } from 'viem'; -import { decodeEventLog } from 'viem/utils'; +import { decodeEventLog, toFunctionSelector } from 'viem/utils'; import { publicDeployAccounts, setup } from './fixtures/utils.js'; import { CrossChainTestHarness } from './shared/cross_chain_test_harness.js'; describe('e2e_public_cross_chain_messaging', () => { + let aztecNode: AztecNode; let pxe: PXE; let deployL1ContractsValues: DeployL1Contracts; let logger: DebugLogger; @@ -48,7 +48,7 @@ describe('e2e_public_cross_chain_messaging', () => { let outbox: any; beforeAll(async () => { - ({ pxe, deployL1ContractsValues, wallets, accounts, logger, teardown } = await setup(2)); + ({ aztecNode, pxe, deployL1ContractsValues, wallets, accounts, logger, teardown } = await setup(2)); user1Wallet = wallets[0]; user2Wallet = wallets[1]; await publicDeployAccounts(wallets[0], accounts.slice(0, 2)); @@ -56,6 +56,7 @@ describe('e2e_public_cross_chain_messaging', () => { beforeEach(async () => { crossChainTestHarness = await CrossChainTestHarness.new( + aztecNode, pxe, deployL1ContractsValues.publicClient, deployL1ContractsValues.walletClient, @@ -88,22 +89,16 @@ describe('e2e_public_cross_chain_messaging', () => { await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); // 2. Deposit tokens to the TokenPortal - await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); - // Wait for the archiver to process the message - await sleep(5000); // waiting 5 seconds. - - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - const unrelatedMintAmount = 99n; - await crossChainTestHarness.mintTokensPublicOnL2(unrelatedMintAmount); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, unrelatedMintAmount); - const balanceBefore = unrelatedMintAmount; + // Wait for the message to be available for consumption + await crossChainTestHarness.makeMessageConsumable(msgLeaf); // 3. Consume L1 -> L2 message and mint public tokens on L2 await crossChainTestHarness.consumeMessageOnAztecAndMintPublicly(bridgeAmount, secret); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, balanceBefore + bridgeAmount); - const afterBalance = balanceBefore + bridgeAmount; + await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount); + const afterBalance = bridgeAmount; // time to withdraw the funds again! logger('Withdrawing funds from L2'); @@ -141,22 +136,16 @@ describe('e2e_public_cross_chain_messaging', () => { const [secret, secretHash] = crossChainTestHarness.generateClaimSecret(); await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); - await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); - // Wait for the archiver to process the message - await sleep(5000); /// waiting 5 seconds. - - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - const unrelatedMintAmount = 99n; - await crossChainTestHarness.mintTokensPublicOnL2(unrelatedMintAmount); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, unrelatedMintAmount); + await crossChainTestHarness.makeMessageConsumable(msgLeaf); const content = Fr.fromBufferReduce( sha256( Buffer.concat([ - keccak(Buffer.from('mint_public(bytes32,uint256,address)')).subarray(0, 4), - serializeToBuffer(...[user2Wallet.getAddress(), new Fr(bridgeAmount), ethAccount.toBuffer32()]), + Buffer.from(toFunctionSelector('mint_public(bytes32,uint256)').substring(2), 'hex'), + serializeToBuffer(...[user2Wallet.getAddress(), new Fr(bridgeAmount)]), ]), ), ); @@ -165,28 +154,18 @@ describe('e2e_public_cross_chain_messaging', () => { new L2Actor(l2Bridge.address, 1), content, secretHash, - 2 ** 32 - 1, - 0, ); // user2 tries to consume this message and minting to itself -> should fail since the message is intended to be consumed only by owner. await expect( - l2Bridge - .withWallet(user2Wallet) - .methods.claim_public(user2Wallet.getAddress(), bridgeAmount, ethAccount, secret) - .simulate(), + l2Bridge.withWallet(user2Wallet).methods.claim_public(user2Wallet.getAddress(), bridgeAmount, secret).simulate(), ).rejects.toThrow(`Message ${wrongMessage.hash().toString()} not found`); // user2 consumes owner's L1-> L2 message on bridge contract and mints public tokens on L2 logger("user2 consumes owner's message on L2 Publicly"); - const tx = l2Bridge - .withWallet(user2Wallet) - .methods.claim_public(ownerAddress, bridgeAmount, ethAccount, secret) - .send(); - const receipt = await tx.wait(); - expect(receipt.status).toBe(TxStatus.MINED); + await l2Bridge.withWallet(user2Wallet).methods.claim_public(ownerAddress, bridgeAmount, secret).send().wait(); // ensure funds are gone to owner and not user2. - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount + unrelatedMintAmount); + await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount); await crossChainTestHarness.expectPublicBalanceOnL2(user2Wallet.getAddress(), 0n); }, 90_000); @@ -210,21 +189,17 @@ describe('e2e_public_cross_chain_messaging', () => { const [secret, secretHash] = crossChainTestHarness.generateClaimSecret(); await crossChainTestHarness.mintTokensOnL1(bridgeAmount); - await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(0n); - // Wait for the archiver to process the message - await sleep(5000); /// waiting 5 seconds. - - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - await crossChainTestHarness.mintTokensPublicOnL2(0n); + await crossChainTestHarness.makeMessageConsumable(msgLeaf); // Wrong message hash const content = Fr.fromBufferReduce( sha256( Buffer.concat([ - keccak(Buffer.from('mint_private(bytes32,uint256,address)')).subarray(0, 4), - serializeToBuffer(...[secretHash, new Fr(bridgeAmount), ethAccount.toBuffer32()]), + Buffer.from(toFunctionSelector('mint_private(bytes32,uint256)').substring(2), 'hex'), + serializeToBuffer(...[secretHash, new Fr(bridgeAmount)]), ]), ), ); @@ -233,13 +208,13 @@ describe('e2e_public_cross_chain_messaging', () => { new L2Actor(l2Bridge.address, 1), content, secretHash, - 2 ** 32 - 1, - 0, ); await expect( - l2Bridge.withWallet(user2Wallet).methods.claim_private(secretHash, bridgeAmount, ethAccount, secret).simulate(), - ).rejects.toThrowError(`Message ${wrongMessage.hash().toString()} not found`); + l2Bridge.withWallet(user2Wallet).methods.claim_private(secretHash, bridgeAmount, secret).simulate(), + ).rejects.toThrowError( + `L1 to L2 message index not found in the store for message ${wrongMessage.hash().toString()}`, + ); }, 60_000); // Note: We register one portal address when deploying contract but that address is no-longer the only address @@ -301,30 +276,24 @@ describe('e2e_public_cross_chain_messaging', () => { 'can send an L1 -> L2 message from a non-registered portal address consumed from private or public', async (isPrivate: boolean) => { const testContract = await TestContract.deploy(user1Wallet).send().deployed(); - - const sender = crossChainTestHarness.ethAccount; - const recipient = testContract.address.toString(); - const secret = Fr.random(); - const secretHash = computeMessageSecretHash(secret); - // The following are arbitrary test values - const content = Fr.random(); - const fee = 0n; - const deadline = 2n ** 32n - 1n; + const message = new L1ToL2Message( + new L1Actor(crossChainTestHarness.ethAccount, crossChainTestHarness.publicClient.chain.id), + new L2Actor(testContract.address, 1), + Fr.random(), // content + computeMessageSecretHash(secret), // secretHash + ); // We inject the message to Inbox - const txHash = await inbox.write.sendL2Message( - [ - { actor: recipient as Hex, version: 1n }, - deadline, - content.toString() as Hex, - secretHash.toString() as Hex, - ] as const, - { value: fee } as any, - ); + const txHash = await inbox.write.sendL2Message([ + { actor: message.recipient.recipient.toString() as Hex, version: 1n }, + message.content.toString() as Hex, + message.secretHash.toString() as Hex, + ] as const); // We check that the message was correctly injected by checking the emitted event + const msgLeaf = message.hash(); { const txReceipt = await crossChainTestHarness.publicClient.waitForTransactionReceipt({ hash: txHash, @@ -333,31 +302,32 @@ describe('e2e_public_cross_chain_messaging', () => { // Exactly 1 event should be emitted in the transaction expect(txReceipt.logs.length).toBe(1); - // We decode the event log before checking it + // We decode the event and get leaf out of it const txLog = txReceipt.logs[0]; const topics = decodeEventLog({ abi: InboxAbi, data: txLog.data, topics: txLog.topics, }); + const receivedMsgLeaf = topics.args.value; - // We check that MessageAdded event was emitted with the expected recipient - // Note: For whatever reason, viem types "think" that there is no recipient on topics.args. I hack around this - // by casting the args to "any" - expect((topics.args as any).recipient).toBe(recipient); + // We check that the leaf inserted into the subtree matches the expected message hash + expect(receivedMsgLeaf).toBe(msgLeaf.toString()); } - // We wait for the archiver to process the message and we push a block for the message to be confirmed - { - await sleep(5000); // waiting 5 seconds. - await testContract.methods.get_this_portal_address().send().wait(); - } + await crossChainTestHarness.makeMessageConsumable(msgLeaf); // Finally, e consume the L1 -> L2 message using the test contract either from private or public if (isPrivate) { - await testContract.methods.consume_message_from_arbitrary_sender_private(content, secret, sender).send().wait(); + await testContract.methods + .consume_message_from_arbitrary_sender_private(message.content, secret, message.sender.sender) + .send() + .wait(); } else { - await testContract.methods.consume_message_from_arbitrary_sender_public(content, secret, sender).send().wait(); + await testContract.methods + .consume_message_from_arbitrary_sender_public(message.content, secret, message.sender.sender) + .send() + .wait(); } }, 60_000, diff --git a/yarn-project/end-to-end/src/e2e_public_to_private_messaging.test.ts b/yarn-project/end-to-end/src/e2e_public_to_private_messaging.test.ts index bfa6f0146d3..78473915a3e 100644 --- a/yarn-project/end-to-end/src/e2e_public_to_private_messaging.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_to_private_messaging.test.ts @@ -1,4 +1,4 @@ -import { AztecAddress, DebugLogger, EthAddress, sleep } from '@aztec/aztec.js'; +import { AztecAddress, DebugLogger, EthAddress } from '@aztec/aztec.js'; import { setup } from './fixtures/utils.js'; import { CrossChainTestHarness } from './shared/cross_chain_test_harness.js'; @@ -16,8 +16,9 @@ describe('e2e_public_to_private_messaging', () => { let crossChainTestHarness: CrossChainTestHarness; beforeEach(async () => { - const { pxe, deployL1ContractsValues, wallet, logger: logger_, teardown: teardown_ } = await setup(2); + const { aztecNode, pxe, deployL1ContractsValues, wallet, logger: logger_, teardown: teardown_ } = await setup(2); crossChainTestHarness = await CrossChainTestHarness.new( + aztecNode, pxe, deployL1ContractsValues.publicClient, deployL1ContractsValues.walletClient, @@ -47,31 +48,25 @@ describe('e2e_public_to_private_messaging', () => { const [secret, secretHash] = crossChainTestHarness.generateClaimSecret(); await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); - await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); + const msgLeaf = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(l1TokenBalance - bridgeAmount); - // Wait for the archiver to process the message - await sleep(5000); /// waiting 5 seconds. - - // Perform another unrelated transaction on L2 to progress the rollup. - const initialBalance = 1n; - await crossChainTestHarness.mintTokensPublicOnL2(initialBalance); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, initialBalance); + await crossChainTestHarness.makeMessageConsumable(msgLeaf); await crossChainTestHarness.consumeMessageOnAztecAndMintPublicly(bridgeAmount, secret); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, initialBalance + bridgeAmount); + await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount); // Create the commitment to be spent in the private domain await crossChainTestHarness.shieldFundsOnL2(shieldAmount, secretHash); // Create the transaction spending the commitment await crossChainTestHarness.redeemShieldPrivatelyOnL2(shieldAmount, secret); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, initialBalance + bridgeAmount - shieldAmount); + await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount - shieldAmount); await crossChainTestHarness.expectPrivateBalanceOnL2(ownerAddress, shieldAmount); // Unshield the tokens again, sending them to the same account, however this can be any account. await crossChainTestHarness.unshieldTokensOnL2(shieldAmount); - await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, initialBalance + bridgeAmount); + await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, bridgeAmount); await crossChainTestHarness.expectPrivateBalanceOnL2(ownerAddress, 0n); }, 200_000); }); diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index b8fb2036055..ae2287cd47a 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -410,16 +410,6 @@ export function getLogger() { return createDebugLogger('aztec:' + describeBlockName); } -// docs:start:delay -/** - * Sleep for a given number of milliseconds. - * @param ms - the number of milliseconds to sleep for - */ -export function delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} -// docs:end:delay - /** * Checks the number of encrypted logs in the last block is as expected. * @param aztecNode - The instance of aztec node for retrieving the logs. diff --git a/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts b/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts deleted file mode 100644 index edc904ebfc9..00000000000 --- a/yarn-project/end-to-end/src/integration_archiver_l1_to_l2.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Archiver, KVArchiverDataStore } from '@aztec/archiver'; -import { AztecNodeConfig } from '@aztec/aztec-node'; -import { - AztecAddress, - CompleteAddress, - DebugLogger, - DeployL1Contracts, - EthAddress, - Fr, - Wallet, - computeMessageSecretHash, -} from '@aztec/aztec.js'; -import { initStoreForRollup, openTmpStore } from '@aztec/kv-store/utils'; -import { TokenContract } from '@aztec/noir-contracts.js/Token'; - -import { Chain, HttpTransport, PublicClient } from 'viem'; - -import { delay, deployAndInitializeTokenAndBridgeContracts, setNextBlockTimestamp, setup } from './fixtures/utils.js'; - -// TODO (#2291) - Replace with token bridge standard -describe('archiver integration with l1 to l2 messages', () => { - let wallet: Wallet; - let archiver: Archiver; - let logger: DebugLogger; - let config: AztecNodeConfig; - let teardown: () => Promise; - - let l2Token: TokenContract; - let ethAccount: EthAddress; - - let tokenPortalAddress: EthAddress; - let tokenPortal: any; - let underlyingERC20: any; - let publicClient: PublicClient; - - let owner: AztecAddress; - let receiver: AztecAddress; - - beforeAll(async () => { - let deployL1ContractsValues: DeployL1Contracts | undefined; - let accounts: CompleteAddress[]; - ({ teardown, wallet, deployL1ContractsValues, accounts, config, logger } = await setup(2)); - config.archiverPollingIntervalMS = 100; - archiver = await Archiver.createAndSync( - { ...config, l1Contracts: deployL1ContractsValues.l1ContractAddresses }, - new KVArchiverDataStore( - await initStoreForRollup(openTmpStore(), deployL1ContractsValues.l1ContractAddresses.rollupAddress), - ), - ); - - const walletClient = deployL1ContractsValues.walletClient; - publicClient = deployL1ContractsValues.publicClient; - - ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]); - owner = accounts[0].address; - receiver = accounts[1].address; - - // Deploy and initialize all required contracts - logger('Deploying Portal, initializing and deploying l2 contract...'); - const contracts = await deployAndInitializeTokenAndBridgeContracts( - wallet, - walletClient, - publicClient, - deployL1ContractsValues!.l1ContractAddresses.registryAddress!, - owner, - ); - l2Token = contracts.token; - underlyingERC20 = contracts.underlyingERC20; - tokenPortal = contracts.tokenPortal; - tokenPortalAddress = contracts.tokenPortalAddress; - logger('Successfully deployed contracts and initialized portal'); - }, 100_000); - - afterAll(async () => { - await archiver.stop(); - await teardown(); - }); - - it('cancelled l1 to l2 messages cannot be consumed by archiver', async () => { - // create a message, then cancel it - const initialL1MintAmount = 1000000n; - const mintAmount = 1000n; - - // Generate a claim secret using pedersen - logger("Generating a claim secret using pedersen's hash function"); - const secret = Fr.random(); - const secretHash = computeMessageSecretHash(secret); - const secretString = `0x${secretHash.toBuffer().toString('hex')}` as `0x${string}`; - logger('Generated claim secret: ' + secretString); - - logger('Minting tokens on L1'); - await underlyingERC20.write.mint([ethAccount.toString(), initialL1MintAmount], {} as any); - expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(initialL1MintAmount); - - // Deposit tokens to the TokenPortal - await underlyingERC20.write.approve([tokenPortalAddress.toString(), mintAmount], {} as any); - const deadline = Number((await publicClient.getBlock()).timestamp + 1000n); - - logger('Sending messages to L1 portal'); - const args = [owner.toString(), mintAmount, ethAccount.toString(), deadline, secretString] as const; - await tokenPortal.write.depositToAztecPublic(args, {} as any); - expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(initialL1MintAmount - mintAmount); - - // Wait for the archiver to process the message - await delay(5000); /// waiting 5 seconds. - - // set the block timestamp to be after the deadline (so we can cancel the message) - await setNextBlockTimestamp(config.rpcUrl, deadline + 1); - - // cancel the message - logger('cancelling the l1 to l2 message'); - const argsCancel = [owner.toString(), mintAmount, deadline, secretString, 0n] as const; - await tokenPortal.write.cancelL1ToAztecMessagePublic(argsCancel, { gas: 1_000_000n } as any); - expect(await underlyingERC20.read.balanceOf([ethAccount.toString()])).toBe(initialL1MintAmount); - // let archiver sync up - await delay(5000); - - // archiver shouldn't have any pending messages. - expect((await archiver.getPendingL1ToL2EntryKeys(10)).length).toEqual(0); - }, 80_000); - - it('archiver handles l1 to l2 message correctly even when l2block has no such messages', async () => { - // send a transfer tx to force through rollup with the message included - await l2Token.methods.transfer_public(owner, receiver, 0n, 0n).send().wait(); - - expect((await archiver.getPendingL1ToL2EntryKeys(10)).length).toEqual(0); - await expect(archiver.getConfirmedL1ToL2Message(Fr.ZERO)).rejects.toThrow(); - }, 30_000); -}); diff --git a/yarn-project/end-to-end/src/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/integration_l1_publisher.test.ts index 608dbb1da33..b98e3eb2759 100644 --- a/yarn-project/end-to-end/src/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/integration_l1_publisher.test.ts @@ -27,7 +27,7 @@ import { fr, makeNewSideEffect, makeNewSideEffectLinkedToNoteHash, makeProof } f import { createEthereumChain } from '@aztec/ethereum'; import { makeTuple, range } from '@aztec/foundation/array'; import { openTmpStore } from '@aztec/kv-store/utils'; -import { AvailabilityOracleAbi, InboxAbi, NewInboxAbi, OutboxAbi, RollupAbi } from '@aztec/l1-artifacts'; +import { AvailabilityOracleAbi, InboxAbi, OutboxAbi, RollupAbi } from '@aztec/l1-artifacts'; import { EmptyRollupProver, L1Publisher, @@ -80,7 +80,6 @@ describe('L1Publisher integration', () => { let rollup: GetContractReturnType>; let inbox: GetContractReturnType>; - let newInbox: GetContractReturnType>; let outbox: GetContractReturnType>; let publisher: L1Publisher; @@ -124,12 +123,7 @@ describe('L1Publisher integration', () => { abi: InboxAbi, client: walletClient, }); - const newInboxAddress = await rollup.read.NEW_INBOX(); - newInbox = getContract({ - address: newInboxAddress, - abi: NewInboxAbi, - client: walletClient, - }); + outbox = getContract({ address: outboxAddress, abi: OutboxAbi, @@ -196,46 +190,32 @@ describe('L1Publisher integration', () => { const sendToL2 = async (content: Fr, recipientAddress: AztecAddress) => { // @todo @LHerskind version hardcoded here (update to bigint or field) const recipient = new L2Actor(recipientAddress, 1); - // Note: using max deadline - const deadline = 2 ** 32 - 1; // getting the 32 byte hex string representation of the content const contentString = content.toString(); // Using the 0 value for the secretHash. const emptySecretHash = Fr.ZERO.toString(); - await newInbox.write.sendL2Message( + const txHash = await inbox.write.sendL2Message( [{ actor: recipient.recipient.toString(), version: BigInt(recipient.version) }, contentString, emptySecretHash], {} as any, ); - // TODO(#4492): Nuke this when purging the old inbox - await inbox.write.sendL2Message( - [ - { actor: recipient.recipient.toString(), version: BigInt(recipient.version) }, - deadline, - contentString, - emptySecretHash, - ], - {} as any, - ); + const txReceipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); - const entry = await inbox.read.computeEntryKey([ - { - sender: { - actor: deployerAccount.address, - chainId: BigInt(publicClient.chain.id), - }, - recipient: { - actor: recipientAddress.toString(), - version: 1n, - }, - content: contentString, - secretHash: emptySecretHash, - deadline, - fee: 0n, - }, - ]); - return Fr.fromString(entry); + // Exactly 1 event should be emitted in the transaction + expect(txReceipt.logs.length).toBe(1); + + // We decode the event log before checking it + const txLog = txReceipt.logs[0]; + const topics = decodeEventLog({ + abi: InboxAbi, + data: txLog.data, + topics: txLog.topics, + }); + + return Fr.fromString(topics.args.value); }; /** @@ -245,7 +225,6 @@ describe('L1Publisher integration', () => { const writeJson = ( fileName: string, block: L2Block, - l1ToL2Messages: Fr[], l1ToL2Content: Fr[], recipientAddress: AztecAddress, deployerAddress: `0x${string}`, @@ -263,7 +242,6 @@ describe('L1Publisher integration', () => { sender: deployerAddress, }, messages: { - l1ToL2Messages: l1ToL2Messages.map(m => `0x${m.toBuffer().toString('hex').padStart(64, '0')}`), l2ToL1Messages: block.body.txEffects .flatMap(txEffect => txEffect.l2ToL1Msgs) .map(m => `0x${m.toBuffer().toString('hex').padStart(64, '0')}`), @@ -318,7 +296,6 @@ describe('L1Publisher integration', () => { }, }, header: `0x${block.header.toBuffer().toString('hex')}`, - l1ToL2MessagesHash: `0x${block.getL1ToL2MessagesHash().toString('hex').padStart(64, '0')}`, publicInputsHash: `0x${block.getPublicInputsHash().toBuffer().toString('hex').padStart(64, '0')}`, }, }; @@ -360,36 +337,17 @@ describe('L1Publisher integration', () => { '0x1647b194c649f5dd01d7c832f89b0f496043c9150797923ea89e93d5ac619a93', ); - let newModelL1ToL2Messages: Fr[] = []; + let currentL1ToL2Messages: Fr[] = []; + let nextL1ToL2Messages: Fr[] = []; + + // We store which tree is about to be consumed so that we can later check the value advanced + let toConsume = await inbox.read.toConsume(); for (let i = 0; i < numberOfConsecutiveBlocks; i++) { const l1ToL2Content = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 128 * i + 1 + 0x400).map(fr); - const l1ToL2Messages: Fr[] = []; for (let j = 0; j < l1ToL2Content.length; j++) { - l1ToL2Messages.push(await sendToL2(l1ToL2Content[j], recipientAddress)); - } - - // check logs - const inboxLogs = await publicClient.getLogs({ - address: inboxAddress, - event: getAbiItem({ - abi: InboxAbi, - name: 'MessageAdded', - }), - fromBlock: blockNumber + 1n, - }); - expect(inboxLogs).toHaveLength(l1ToL2Messages.length * (i + 1)); - for (let j = 0; j < NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP; j++) { - const event = inboxLogs[j + i * NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP].args; - expect(event.content).toEqual(l1ToL2Content[j].toString()); - expect(event.deadline).toEqual(2 ** 32 - 1); - expect(event.entryKey).toEqual(l1ToL2Messages[j].toString()); - expect(event.fee).toEqual(0n); - expect(event.recipient).toEqual(recipientAddress.toString()); - expect(event.recipientVersion).toEqual(1n); - expect(event.senderChainId).toEqual(BigInt(publicClient.chain.id)); - expect(event.sender).toEqual(deployerAccount.address); + nextL1ToL2Messages.push(await sendToL2(l1ToL2Content[j], recipientAddress)); } // Ensure that each transaction has unique (non-intersecting nullifier values) @@ -409,17 +367,9 @@ describe('L1Publisher integration', () => { coinbase, feeRecipient, ); - const [block] = await builder.buildL2Block(globalVariables, txs, newModelL1ToL2Messages, l1ToL2Messages); + const [block] = await builder.buildL2Block(globalVariables, txs, currentL1ToL2Messages); prevHeader = block.header; - // check that values are in the inbox - for (let j = 0; j < l1ToL2Messages.length; j++) { - if (l1ToL2Messages[j].isZero()) { - continue; - } - expect(await inbox.read.contains([l1ToL2Messages[j].toString()])).toBeTruthy(); - } - const newL2ToL1MsgsArray = block.body.txEffects.flatMap(txEffect => txEffect.l2ToL1Msgs); // check that values are not in the outbox @@ -427,7 +377,7 @@ describe('L1Publisher integration', () => { expect(await outbox.read.contains([newL2ToL1MsgsArray[j].toString()])).toBeFalsy(); } - writeJson(`mixed_block_${i}`, block, l1ToL2Messages, l1ToL2Content, recipientAddress, deployerAccount.address); + writeJson(`mixed_block_${i}`, block, l1ToL2Content, recipientAddress, deployerAccount.address); await publisher.processL2Block(block); @@ -458,21 +408,20 @@ describe('L1Publisher integration', () => { }); expect(ethTx.input).toEqual(expectedData); - // check that values have been consumed from the inbox - for (let j = 0; j < l1ToL2Messages.length; j++) { - if (l1ToL2Messages[j].isZero()) { - continue; - } - expect(await inbox.read.contains([l1ToL2Messages[j].toString()])).toBeFalsy(); - } + // Check a tree have been consumed from the inbox + const newToConsume = await inbox.read.toConsume(); + expect(newToConsume).toEqual(toConsume + 1n); + toConsume = newToConsume; // check that values are inserted into the outbox for (let j = 0; j < newL2ToL1MsgsArray.length; j++) { expect(await outbox.read.contains([newL2ToL1MsgsArray[j].toString()])).toBeTruthy(); } - // There is a 1 block lag in the new model - newModelL1ToL2Messages = l1ToL2Messages; + // There is a 1 block lag between before messages get consumed from the inbox + currentL1ToL2Messages = nextL1ToL2Messages; + // We wipe the messages from previous iteration + nextL1ToL2Messages = []; } }, 360_000); @@ -494,10 +443,10 @@ describe('L1Publisher integration', () => { coinbase, feeRecipient, ); - const [block] = await builder.buildL2Block(globalVariables, txs, l1ToL2Messages, l1ToL2Messages); + const [block] = await builder.buildL2Block(globalVariables, txs, l1ToL2Messages); prevHeader = block.header; - writeJson(`empty_block_${i}`, block, l1ToL2Messages, [], AztecAddress.ZERO, deployerAccount.address); + writeJson(`empty_block_${i}`, block, [], AztecAddress.ZERO, deployerAccount.address); await publisher.processL2Block(block); diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index 2cb04e59e67..ceb75a21bba 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -1,6 +1,7 @@ // docs:start:cross_chain_test_harness import { AztecAddress, + AztecNode, DebugLogger, EthAddress, ExtendedNote, @@ -8,11 +9,11 @@ import { Note, PXE, TxHash, - TxStatus, Wallet, computeMessageSecretHash, deployL1Contract, sha256, + sleep, } from '@aztec/aztec.js'; import { InboxAbi, @@ -25,7 +26,7 @@ import { import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { TokenBridgeContract } from '@aztec/noir-contracts.js/TokenBridge'; -import { Account, Chain, HttpTransport, PublicClient, WalletClient, getContract, getFunctionSelector } from 'viem'; +import { Account, Chain, HttpTransport, PublicClient, WalletClient, getContract, toFunctionSelector } from 'viem'; // docs:start:deployAndInitializeTokenAndBridgeContracts /** @@ -122,6 +123,7 @@ export async function deployAndInitializeTokenAndBridgeContracts( */ export class CrossChainTestHarness { static async new( + aztecNode: AztecNode, pxeService: PXE, publicClient: PublicClient, walletClient: any, @@ -159,6 +161,7 @@ export class CrossChainTestHarness { logger('Deployed and initialized token, portal and its bridge.'); return new CrossChainTestHarness( + aztecNode, pxeService, logger, token, @@ -176,6 +179,8 @@ export class CrossChainTestHarness { } constructor( + /** Aztec node instance. */ + public aztecNode: AztecNode, /** Private eXecution Environment (PXE). */ public pxeService: PXE, /** Logger. */ @@ -235,16 +240,8 @@ export class CrossChainTestHarness { await this.publicClient.waitForTransactionReceipt({ hash: txHash1 }); // Deposit tokens to the TokenPortal - const deadline = 2 ** 32 - 1; // max uint32 - this.logger('Sending messages to L1 portal to be consumed publicly'); - const args = [ - this.ownerAddress.toString(), - bridgeAmount, - this.ethAccount.toString(), - deadline, - secretHash.toString(), - ] as const; + const args = [this.ownerAddress.toString(), bridgeAmount, secretHash.toString()] as const; const { result: entryKeyHex } = await this.tokenPortal.simulate.depositToAztecPublic(args, { account: this.ethAccount.toString(), } as any); @@ -265,14 +262,10 @@ export class CrossChainTestHarness { ); await this.publicClient.waitForTransactionReceipt({ hash: txHash1 }); // Deposit tokens to the TokenPortal - const deadline = 2 ** 32 - 1; // max uint32 - this.logger('Sending messages to L1 portal to be consumed privately'); const args = [ secretHashForRedeemingMintedNotes.toString(), bridgeAmount, - this.ethAccount.toString(), - deadline, secretHashForL2MessageConsumption.toString(), ] as const; const { result: entryKeyHex } = await this.tokenPortal.simulate.depositToAztecPrivate(args, { @@ -286,39 +279,30 @@ export class CrossChainTestHarness { async mintTokensPublicOnL2(amount: bigint) { this.logger('Minting tokens on L2 publicly'); - const tx = this.l2Token.methods.mint_public(this.ownerAddress, amount).send(); - const receipt = await tx.wait(); - expect(receipt.status).toBe(TxStatus.MINED); + await this.l2Token.methods.mint_public(this.ownerAddress, amount).send().wait(); } async mintTokensPrivateOnL2(amount: bigint, secretHash: Fr) { - const tx = this.l2Token.methods.mint_private(amount, secretHash).send(); - const receipt = await tx.wait(); - expect(receipt.status).toBe(TxStatus.MINED); + const receipt = await this.l2Token.methods.mint_private(amount, secretHash).send().wait(); await this.addPendingShieldNoteToPXE(amount, secretHash, receipt.txHash); } async performL2Transfer(transferAmount: bigint, receiverAddress: AztecAddress) { // send a transfer tx to force through rollup with the message included - const transferTx = this.l2Token.methods - .transfer_public(this.ownerAddress, receiverAddress, transferAmount, 0) - .send(); - const receipt = await transferTx.wait(); - expect(receipt.status).toBe(TxStatus.MINED); + await this.l2Token.methods.transfer_public(this.ownerAddress, receiverAddress, transferAmount, 0).send().wait(); } - async consumeMessageOnAztecAndMintSecretly( + async consumeMessageOnAztecAndMintPrivately( secretHashForRedeemingMintedNotes: Fr, bridgeAmount: bigint, secretForL2MessageConsumption: Fr, ) { - this.logger('Consuming messages on L2 secretively'); + this.logger('Consuming messages on L2 privately'); // Call the mint tokens function on the Aztec.nr contract - const consumptionTx = this.l2Bridge.methods - .claim_private(secretHashForRedeemingMintedNotes, bridgeAmount, this.ethAccount, secretForL2MessageConsumption) - .send(); - const consumptionReceipt = await consumptionTx.wait(); - expect(consumptionReceipt.status).toBe(TxStatus.MINED); + const consumptionReceipt = await this.l2Bridge.methods + .claim_private(secretHashForRedeemingMintedNotes, bridgeAmount, secretForL2MessageConsumption) + .send() + .wait(); await this.addPendingShieldNoteToPXE(bridgeAmount, secretHashForRedeemingMintedNotes, consumptionReceipt.txHash); } @@ -326,25 +310,21 @@ export class CrossChainTestHarness { async consumeMessageOnAztecAndMintPublicly(bridgeAmount: bigint, secret: Fr) { this.logger('Consuming messages on L2 Publicly'); // Call the mint tokens function on the Aztec.nr contract - const tx = this.l2Bridge.methods.claim_public(this.ownerAddress, bridgeAmount, this.ethAccount, secret).send(); - const receipt = await tx.wait(); - expect(receipt.status).toBe(TxStatus.MINED); + await this.l2Bridge.methods.claim_public(this.ownerAddress, bridgeAmount, secret).send().wait(); } async withdrawPrivateFromAztecToL1(withdrawAmount: bigint, nonce: Fr = Fr.ZERO) { - const withdrawTx = this.l2Bridge.methods + await this.l2Bridge.methods .exit_to_l1_private(this.l2Token.address, this.ethAccount, withdrawAmount, EthAddress.ZERO, nonce) - .send(); - const withdrawReceipt = await withdrawTx.wait(); - expect(withdrawReceipt.status).toBe(TxStatus.MINED); + .send() + .wait(); } async withdrawPublicFromAztecToL1(withdrawAmount: bigint, nonce: Fr = Fr.ZERO) { - const withdrawTx = this.l2Bridge.methods + await this.l2Bridge.methods .exit_to_l1_public(this.ethAccount, withdrawAmount, EthAddress.ZERO, nonce) - .send(); - const withdrawReceipt = await withdrawTx.wait(); - expect(withdrawReceipt.status).toBe(TxStatus.MINED); + .send() + .wait(); } async getL2PrivateBalanceOf(owner: AztecAddress) { @@ -372,7 +352,7 @@ export class CrossChainTestHarness { const content = Fr.fromBufferReduce( sha256( Buffer.concat([ - Buffer.from(getFunctionSelector('withdraw(address,uint256,address)').substring(2), 'hex'), + Buffer.from(toFunctionSelector('withdraw(address,uint256,address)').substring(2), 'hex'), this.ethAccount.toBuffer32(), new Fr(withdrawAmount).toBuffer(), callerOnL1.toBuffer32(), @@ -413,9 +393,10 @@ export class CrossChainTestHarness { async shieldFundsOnL2(shieldAmount: bigint, secretHash: Fr) { this.logger('Shielding funds on L2'); - const shieldTx = this.l2Token.methods.shield(this.ownerAddress, shieldAmount, secretHash, 0).send(); - const shieldReceipt = await shieldTx.wait(); - expect(shieldReceipt.status).toBe(TxStatus.MINED); + const shieldReceipt = await this.l2Token.methods + .shield(this.ownerAddress, shieldAmount, secretHash, 0) + .send() + .wait(); await this.addPendingShieldNoteToPXE(shieldAmount, secretHash, shieldReceipt.txHash); } @@ -438,18 +419,41 @@ export class CrossChainTestHarness { async redeemShieldPrivatelyOnL2(shieldAmount: bigint, secret: Fr) { this.logger('Spending note in private call'); - const privateTx = this.l2Token.methods.redeem_shield(this.ownerAddress, shieldAmount, secret).send(); - const privateReceipt = await privateTx.wait(); - expect(privateReceipt.status).toBe(TxStatus.MINED); + await this.l2Token.methods.redeem_shield(this.ownerAddress, shieldAmount, secret).send().wait(); } async unshieldTokensOnL2(unshieldAmount: bigint, nonce = Fr.ZERO) { this.logger('Unshielding tokens'); - const unshieldTx = this.l2Token.methods - .unshield(this.ownerAddress, this.ownerAddress, unshieldAmount, nonce) - .send(); - const unshieldReceipt = await unshieldTx.wait(); - expect(unshieldReceipt.status).toBe(TxStatus.MINED); + await this.l2Token.methods.unshield(this.ownerAddress, this.ownerAddress, unshieldAmount, nonce).send().wait(); + } + + /** + * Makes message available for consumption. + * @dev Does that by performing 2 unrelated transactions on L2 to progress the rollup by 2 blocks and then waits for + * message to be processed by archiver. We need to progress by 2 because there is a 1 block lag between when + * the message is sent to Inbox and when the subtree containing the message is included in the block and then when + * it's included it becomes available for consumption in the next block because the l1 to l2 message tree. + */ + async makeMessageConsumable(msgLeaf: Fr) { + const messageBlock = Number(await this.inbox.read.inProgress()); + await this.mintTokensPublicOnL2(0n); + await this.mintTokensPublicOnL2(0n); + + // We poll getL1ToL2MessageIndexAndSiblingPath endpoint until the message is available (it's most likely already + // available given that we waited for 2 blocks). + let i = 0; + while (i < 5) { + try { + // The function throws if message is not found + await this.aztecNode.getL1ToL2MessageIndexAndSiblingPath(messageBlock, msgLeaf); + } catch (e) { + i++; + await sleep(1000); + continue; + } + return; + } + throw new Error('Message not available after 5 seconds'); } } // docs:end:cross_chain_test_harness diff --git a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts index 4f34d8242ec..cf368845f2d 100644 --- a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts @@ -8,7 +8,6 @@ import { Wallet, computeMessageSecretHash, deployL1Contract, - sleep, } from '@aztec/aztec.js'; import { GasPortalAbi, GasPortalBytecode, OutboxAbi, PortalERC20Abi, PortalERC20Bytecode } from '@aztec/l1-artifacts'; import { GasTokenContract } from '@aztec/noir-contracts.js'; @@ -219,16 +218,8 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness { await this.underlyingERC20.write.approve([this.tokenPortalAddress.toString(), bridgeAmount], {} as any); // Deposit tokens to the TokenPortal - const deadline = 2 ** 32 - 1; // max uint32 - this.logger('Sending messages to L1 portal to be consumed publicly'); - const args = [ - l2Address.toString(), - bridgeAmount, - this.ethAccount.toString(), - deadline, - secretHash.toString(), - ] as const; + const args = [l2Address.toString(), bridgeAmount, secretHash.toString()] as const; const { result: entryKeyHex } = await this.tokenPortal.simulate.depositToAztecPublic(args, { account: this.ethAccount.toString(), } as any); @@ -266,7 +257,7 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness { async consumeMessageOnAztecAndMintPublicly(bridgeAmount: bigint, owner: AztecAddress, secret: Fr) { this.logger('Consuming messages on L2 Publicly'); // Call the mint tokens function on the Aztec.nr contract - const tx = this.l2Token.methods.claim_public(owner, bridgeAmount, this.ethAccount, secret).send(); + const tx = this.l2Token.methods.claim_public(owner, bridgeAmount, secret).send(); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); } @@ -290,10 +281,8 @@ class GasBridgingTestHarness implements IGasBridgingTestHarness { await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash); expect(await this.getL1BalanceOf(this.ethAccount)).toBe(l1TokenBalance - bridgeAmount); - // Wait for the archiver to process the message - await sleep(2500); - - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. + // Perform an unrelated transactions on L2 to progress the rollup by 2 blocks. + await this.l2Token.methods.check_balance(0).send().wait(); await this.l2Token.methods.check_balance(0).send().wait(); // 3. Consume L1-> L2 message and mint public tokens on L2 diff --git a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts index 6bdaf5caaae..00ab5e70468 100644 --- a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts +++ b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts @@ -1,20 +1,20 @@ import { AccountWallet, AztecAddress, + AztecNode, DebugLogger, EthAddress, Fr, PXE, TxStatus, computeAuthWitMessageHash, - sleep, } from '@aztec/aztec.js'; import { deployL1Contract } from '@aztec/ethereum'; -import { UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-artifacts'; +import { InboxAbi, UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-artifacts'; import { UniswapContract } from '@aztec/noir-contracts.js/Uniswap'; import { jest } from '@jest/globals'; -import { Chain, HttpTransport, PublicClient, getContract, parseEther } from 'viem'; +import { Chain, HttpTransport, PublicClient, decodeEventLog, getContract, parseEther } from 'viem'; import { publicDeployAccounts } from '../fixtures/utils.js'; import { CrossChainTestHarness } from './cross_chain_test_harness.js'; @@ -31,6 +31,8 @@ const TIMEOUT = 360_000; /** Objects to be returned by the uniswap setup function */ export type UniswapSetupContext = { + /** Aztec Node instance */ + aztecNode: AztecNode; /** The Private eXecution Environment (PXE). */ pxe: PXE; /** Logger instance named as the current test. */ @@ -58,6 +60,7 @@ export const uniswapL1L2TestSuite = ( const WETH9_ADDRESS: EthAddress = EthAddress.fromString('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'); const DAI_ADDRESS: EthAddress = EthAddress.fromString('0x6B175474E89094C44Da98b954EedeAC495271d0F'); + let aztecNode: AztecNode; let pxe: PXE; let logger: DebugLogger; @@ -80,11 +83,10 @@ export const uniswapL1L2TestSuite = ( const wethAmountToBridge = parseEther('1'); const uniswapFeeTier = 3000n; const minimumOutputAmount = 0n; - const deadlineForDepositingSwappedDai = BigInt(2 ** 32 - 1); // max uint32 beforeAll(async () => { let publicClient: PublicClient; - ({ pxe, logger, publicClient, walletClient, ownerWallet, sponsorWallet } = await setup()); + ({ aztecNode, pxe, logger, publicClient, walletClient, ownerWallet, sponsorWallet } = await setup()); // walletClient = deployL1ContractsValues.walletClient; // const publicClient = deployL1ContractsValues.publicClient; @@ -101,6 +103,7 @@ export const uniswapL1L2TestSuite = ( logger('Deploying DAI Portal, initializing and deploying l2 contract...'); daiCrossChainHarness = await CrossChainTestHarness.new( + aztecNode, pxe, publicClient, walletClient, @@ -111,6 +114,7 @@ export const uniswapL1L2TestSuite = ( logger('Deploying WETH Portal, initializing and deploying l2 contract...'); wethCrossChainHarness = await CrossChainTestHarness.new( + aztecNode, pxe, publicClient, walletClient, @@ -156,12 +160,15 @@ export const uniswapL1L2TestSuite = ( // docs:start:uniswap_private it('should uniswap trade on L1 from L2 funds privately (swaps WETH -> DAI)', async () => { const wethL1BeforeBalance = await wethCrossChainHarness.getL1BalanceOf(ownerEthAddress); + if (wethL1BeforeBalance < wethAmountToBridge) { + throw new Error('Not enough WETH to run this test. Try restarting anvil.'); + } // 1. Approve and deposit weth to the portal and move to L2 const [secretForMintingWeth, secretHashForMintingWeth] = wethCrossChainHarness.generateClaimSecret(); const [secretForRedeemingWeth, secretHashForRedeemingWeth] = wethCrossChainHarness.generateClaimSecret(); - const entryKey = await wethCrossChainHarness.sendTokensToPortalPrivate( + const tokenDepositMsgLeaf = await wethCrossChainHarness.sendTokensToPortalPrivate( secretHashForRedeemingWeth, wethAmountToBridge, secretHashForMintingWeth, @@ -173,17 +180,12 @@ export const uniswapL1L2TestSuite = ( expect(await wethCrossChainHarness.getL1BalanceOf(wethCrossChainHarness.tokenPortalAddress)).toBe( wethAmountToBridge, ); - expect(await wethCrossChainHarness.inbox.read.contains([entryKey.toString()])).toBe(true); - - // Wait for the archiver to process the message - await sleep(5000); - // Perform an unrelated transaction on L2 to progress the rollup. Here we mint public tokens. - await wethCrossChainHarness.mintTokensPublicOnL2(0n); + await wethCrossChainHarness.makeMessageConsumable(tokenDepositMsgLeaf); // 2. Claim WETH on L2 logger('Minting weth on L2'); - await wethCrossChainHarness.consumeMessageOnAztecAndMintSecretly( + await wethCrossChainHarness.consumeMessageOnAztecAndMintPrivately( secretHashForRedeemingWeth, wethAmountToBridge, secretForMintingWeth, @@ -227,8 +229,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, secretHashForRedeemingDai, secretHashForDepositingSwappedDai, - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, ) .send() @@ -255,13 +255,27 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, secretHashForRedeemingDai.toString(), secretHashForDepositingSwappedDai.toString(), - deadlineForDepositingSwappedDai, - ownerEthAddress.toString(), true, ] as const; // this should also insert a message into the inbox. - await uniswapPortal.write.swapPrivate(swapArgs, {} as any); + const txHash = await uniswapPortal.write.swapPrivate(swapArgs, {} as any); + + // We get the msg leaf from event so that we can later wait for it to be available for consumption + let tokenOutMsgLeaf: Fr; + { + const txReceipt = await daiCrossChainHarness.publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + const txLog = txReceipt.logs[9]; + const topics = decodeEventLog({ + abi: InboxAbi, + data: txLog.data, + topics: txLog.topics, + }); + tokenOutMsgLeaf = Fr.fromString(topics.args.value); + } // weth was swapped to dai and send to portal const daiL1BalanceOfPortalAfter = await daiCrossChainHarness.getL1BalanceOf( @@ -270,14 +284,12 @@ export const uniswapL1L2TestSuite = ( expect(daiL1BalanceOfPortalAfter).toBeGreaterThan(daiL1BalanceOfPortalBeforeSwap); const daiAmountToBridge = BigInt(daiL1BalanceOfPortalAfter - daiL1BalanceOfPortalBeforeSwap); - // Wait for the archiver to process the message - await sleep(5000); - // send a transfer tx to force through rollup with the message included - await wethCrossChainHarness.mintTokensPublicOnL2(0n); + // Wait for the message to be available for consumption + await daiCrossChainHarness.makeMessageConsumable(tokenOutMsgLeaf); // 6. claim dai on L2 logger('Consuming messages to mint dai on L2'); - await daiCrossChainHarness.consumeMessageOnAztecAndMintSecretly( + await daiCrossChainHarness.consumeMessageOnAztecAndMintPrivately( secretHashForRedeemingDai, daiAmountToBridge, secretForDepositingSwappedDai, @@ -303,7 +315,7 @@ export const uniswapL1L2TestSuite = ( // 1. Approve and deposit weth to the portal and move to L2 const [secretForMintingWeth, secretHashForMintingWeth] = wethCrossChainHarness.generateClaimSecret(); - const entryKey = await wethCrossChainHarness.sendTokensToPortalPublic( + const wethDepositMsgLeaf = await wethCrossChainHarness.sendTokensToPortalPublic( wethAmountToBridge, secretHashForMintingWeth, ); @@ -314,13 +326,9 @@ export const uniswapL1L2TestSuite = ( expect(await wethCrossChainHarness.getL1BalanceOf(wethCrossChainHarness.tokenPortalAddress)).toBe( wethAmountToBridge, ); - expect(await wethCrossChainHarness.inbox.read.contains([entryKey.toString()])).toBe(true); - // Wait for the archiver to process the message - await sleep(5000); - - // Perform an unrelated transaction on L2 to progress the rollup. Here we transfer 0 tokens - await wethCrossChainHarness.mintTokensPublicOnL2(0n); + // Wait for the message to be available for consumption + await wethCrossChainHarness.makeMessageConsumable(wethDepositMsgLeaf); // 2. Claim WETH on L2 logger('Minting weth on L2'); @@ -363,8 +371,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress, secretHashForDepositingSwappedDai, - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, nonceForSwap, ); @@ -395,13 +401,28 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress.toString(), secretHashForDepositingSwappedDai.toString(), - deadlineForDepositingSwappedDai, - ownerEthAddress.toString(), true, ] as const; // this should also insert a message into the inbox. - await uniswapPortal.write.swapPublic(swapArgs, {} as any); + const txHash = await uniswapPortal.write.swapPublic(swapArgs, {} as any); + + // We get the msg leaf from event so that we can later wait for it to be available for consumption + let outTokenDepositMsgLeaf: Fr; + { + const txReceipt = await daiCrossChainHarness.publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + const txLog = txReceipt.logs[9]; + const topics = decodeEventLog({ + abi: InboxAbi, + data: txLog.data, + topics: txLog.topics, + }); + outTokenDepositMsgLeaf = Fr.fromString(topics.args.value); + } + // weth was swapped to dai and send to portal const daiL1BalanceOfPortalAfter = await daiCrossChainHarness.getL1BalanceOf( daiCrossChainHarness.tokenPortalAddress, @@ -409,10 +430,8 @@ export const uniswapL1L2TestSuite = ( expect(daiL1BalanceOfPortalAfter).toBeGreaterThan(daiL1BalanceOfPortalBeforeSwap); const daiAmountToBridge = BigInt(daiL1BalanceOfPortalAfter - daiL1BalanceOfPortalBeforeSwap); - // Wait for the archiver to process the message - await sleep(5000); - // send a transfer tx to force through rollup with the message included - await wethCrossChainHarness.mintTokensPublicOnL2(0n); + // Wait for the message to be available for consumption + await daiCrossChainHarness.makeMessageConsumable(outTokenDepositMsgLeaf); // 6. claim dai on L2 logger('Consuming messages to mint dai on L2'); @@ -456,8 +475,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, Fr.random(), Fr.random(), - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, ) .simulate(), @@ -496,8 +513,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, Fr.random(), Fr.random(), - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, ) .simulate(), @@ -534,8 +549,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress, secretHashForDepositingSwappedDai, - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, Fr.ZERO, // nonce for swap -> doesn't matter ) @@ -565,8 +578,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress, secretHashForDepositingSwappedDai, - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, nonceForSwap, ); @@ -603,8 +614,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress, Fr.random(), - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, Fr.ZERO, ) @@ -648,8 +657,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, Fr.random(), secretHashForDepositingSwappedDai, - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, ) .send() @@ -668,8 +675,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress.toString(), secretHashForDepositingSwappedDai.toString(), - deadlineForDepositingSwappedDai, - ownerEthAddress.toString(), true, ] as const; await expect( @@ -706,8 +711,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, ownerAddress, secretHashForDepositingSwappedDai, - deadlineForDepositingSwappedDai, - ownerEthAddress, ownerEthAddress, Fr.ZERO, ) @@ -728,8 +731,6 @@ export const uniswapL1L2TestSuite = ( minimumOutputAmount, secretHashForRedeemingDai.toString(), secretHashForDepositingSwappedDai.toString(), - deadlineForDepositingSwappedDai, - ownerEthAddress.toString(), true, ] as const; await expect( diff --git a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts index 5e20be7f9f5..461e8307020 100644 --- a/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts +++ b/yarn-project/end-to-end/src/uniswap_trade_on_l1_from_l2.test.ts @@ -12,6 +12,7 @@ let teardown: () => Promise; // docs:start:uniswap_setup const testSetup = async (): Promise => { const { + aztecNode, teardown: teardown_, pxe, deployL1ContractsValues, @@ -27,7 +28,7 @@ const testSetup = async (): Promise => { teardown = teardown_; - return { pxe, logger, publicClient, walletClient, ownerWallet, sponsorWallet }; + return { aztecNode, pxe, logger, publicClient, walletClient, ownerWallet, sponsorWallet }; }; // docs:end:uniswap_setup diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 3d05513b621..c21ebace469 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -114,15 +114,6 @@ export const deployL1Contracts = async ( ); logger(`Deployed Registry at ${registryAddress}`); - const inboxAddress = await deployL1Contract( - walletClient, - publicClient, - contractsToDeploy.inbox.contractAbi, - contractsToDeploy.inbox.contractBytecode, - [getAddress(registryAddress.toString())], - ); - logger(`Deployed Inbox at ${inboxAddress}`); - const outboxAddress = await deployL1Contract( walletClient, publicClient, @@ -149,6 +140,17 @@ export const deployL1Contracts = async ( ); logger(`Deployed Rollup at ${rollupAddress}`); + // Inbox is immutable and is deployed from Rollup's constructor so we just fetch it from the contract. + let inboxAddress!: EthAddress; + { + const rollup = getContract({ + address: getAddress(rollupAddress.toString()), + abi: contractsToDeploy.rollup.contractAbi, + client: publicClient, + }); + inboxAddress = EthAddress.fromString((await rollup.read.INBOX([])) as any); + } + // We need to call a function on the registry to set the various contract addresses. const registryContract = getContract({ address: getAddress(registryAddress.toString()), diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index 83e239bf1f8..67ba59a51a1 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -13,7 +13,6 @@ CONTRACTS=( "l1-contracts:AvailabilityOracle" "l1-contracts:Registry" "l1-contracts:Inbox" - "l1-contracts:NewInbox" "l1-contracts:Outbox" "l1-contracts:Rollup" "l1-contracts:TokenPortal" diff --git a/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap b/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap index 200b4dc53bc..c2721e78317 100644 --- a/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap +++ b/yarn-project/protocol-contracts/src/gas-token/__snapshots__/index.test.ts.snap @@ -2,10 +2,10 @@ exports[`GasToken returns canonical protocol contract 1`] = ` { - "address": AztecAddress<0x2ced754038b131e7afe3f95263c4bf406403817dea3f2bbc45af5125a23df041>, + "address": AztecAddress<0x1ab834e6b4aaf48b35c8b39f86db61b305be30f2a7de68fe5d471d11997c3251>, "instance": { - "address": AztecAddress<0x2ced754038b131e7afe3f95263c4bf406403817dea3f2bbc45af5125a23df041>, - "contractClassId": Fr<0x022f4fb4c9521c04fb71f2d1c221cf3f9a4fe7116f57e615cc8e878a91bf7de6>, + "address": AztecAddress<0x1ab834e6b4aaf48b35c8b39f86db61b305be30f2a7de68fe5d471d11997c3251>, + "contractClassId": Fr<0x0bb42885138b96238ca70f89755efb1317412ad9a346ba4bc01e356fd0c724a7>, "deployer": AztecAddress<0x0000000000000000000000000000000000000000000000000000000000000000>, "initializationHash": Fr<0x0000000000000000000000000000000000000000000000000000000000000000>, "portalContractAddress": EthAddress<0x0000000000000000000000000000000000000000>, @@ -19,10 +19,10 @@ exports[`GasToken returns canonical protocol contract 1`] = ` exports[`GasToken returns canonical protocol contract 2`] = ` { "artifactHash": Fr<0x18af4bb0ca6fe07d0ae6da493b2c7b1af038ee904721dbba9b6e571e6d495726>, - "id": Fr<0x022f4fb4c9521c04fb71f2d1c221cf3f9a4fe7116f57e615cc8e878a91bf7de6>, + "id": Fr<0x0bb42885138b96238ca70f89755efb1317412ad9a346ba4bc01e356fd0c724a7>, "privateFunctions": [], "privateFunctionsRoot": Fr<0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f>, - "publicBytecodeCommitment": Fr<0x1626349ecd0025fad0d794b6c15423d679a77ef4319ac99cf98eec07b027c844>, + "publicBytecodeCommitment": Fr<0x02f8f0edea3e7909df7193c86dedfc62dc44a3057332524e49ecdee22733fb93>, "version": 1, } `; @@ -35,15 +35,15 @@ exports[`GasToken returns canonical protocol contract 3`] = ` }, { "isInternal": false, - "selector": Selector<0x69e04dfd>, + "selector": Selector<0x5b7863df>, }, { "isInternal": false, - "selector": Selector<0x6bfd1d5b>, + "selector": Selector<0x69e04dfd>, }, { "isInternal": false, - "selector": Selector<0xcbfc21a6>, + "selector": Selector<0x6bfd1d5b>, }, ] `; diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 63f84b0e970..f21b842a336 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -129,9 +129,7 @@ export class SimulatorOracle implements DBOracle { * index of the message in the l1ToL2MessageTree */ async getL1ToL2MembershipWitness(entryKey: Fr): Promise> { - const messageAndIndex = await this.aztecNode.getL1ToL2MessageAndIndex(entryKey); - const index = messageAndIndex.index; - const siblingPath = await this.aztecNode.getL1ToL2MessageSiblingPath('latest', index); + const [index, siblingPath] = await this.aztecNode.getL1ToL2MessageIndexAndSiblingPath('latest', entryKey); return new MessageLoadOracleInputs(index, siblingPath); } diff --git a/yarn-project/sequencer-client/src/block_builder/index.ts b/yarn-project/sequencer-client/src/block_builder/index.ts index d4d392ae6cc..7f2fefca322 100644 --- a/yarn-project/sequencer-client/src/block_builder/index.ts +++ b/yarn-project/sequencer-client/src/block_builder/index.ts @@ -13,14 +13,8 @@ export interface BlockBuilder { * Note that the number of txs need to be a power of two. * @param globalVariables - Global variables to include in the block. * @param txs - Processed txs to include. - * @param newModelL1ToL2Messages - L1 to L2 messages emitted by the new inbox. - * @param newL1ToL2Messages - L1 to L2 messages to be part of the block. + * @param l1ToL2Messages - L1 to L2 messages to be part of the block. * @returns The new L2 block along with its proof from the root circuit. */ - buildL2Block( - globalVariables: GlobalVariables, - txs: ProcessedTx[], - newModelL1ToL2Messages: Fr[], // TODO(#4492): Rename this when purging the old inbox - newL1ToL2Messages: Fr[], // TODO(#4492): Nuke this when purging the old inbox - ): Promise<[L2Block, Proof]>; + buildL2Block(globalVariables: GlobalVariables, txs: ProcessedTx[], l1ToL2Messages: Fr[]): Promise<[L2Block, Proof]>; } diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts index 0a647fa0f56..2030f5b482c 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts @@ -82,8 +82,7 @@ describe('sequencer/solo_block_builder', () => { let baseRollupOutputLeft: BaseOrMergeRollupPublicInputs; let baseRollupOutputRight: BaseOrMergeRollupPublicInputs; let rootRollupOutput: RootRollupPublicInputs; - let newModelMockL1ToL2Messages: Fr[]; // TODO(#4492): Rename this when purging the old inbox - let mockL1ToL2Messages: Fr[]; // TODO(#4492): Nuke this when purging the old inbox + let mockL1ToL2Messages: Fr[]; let globalVariables: GlobalVariables; @@ -106,7 +105,6 @@ describe('sequencer/solo_block_builder', () => { builder = new SoloBlockBuilder(builderDb, vks, simulator, prover); // Create mock l1 to L2 messages - newModelMockL1ToL2Messages = new Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)); mockL1ToL2Messages = new Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)); // Create mock outputs for simulator @@ -285,12 +283,7 @@ describe('sequencer/solo_block_builder', () => { const txs = await buildMockSimulatorInputs(); // Actually build a block! - const [l2Block, proof] = await builder.buildL2Block( - globalVariables, - txs, - newModelMockL1ToL2Messages, - mockL1ToL2Messages, - ); + const [l2Block, proof] = await builder.buildL2Block(globalVariables, txs, mockL1ToL2Messages); expect(l2Block.number).toEqual(blockNumber); expect(proof).toEqual(emptyProof); @@ -300,9 +293,7 @@ describe('sequencer/solo_block_builder', () => { // Assemble a fake transaction const txs = await buildMockSimulatorInputs(); const l1ToL2Messages = new Array(100).fill(new Fr(0n)); - await expect( - builder.buildL2Block(globalVariables, txs, newModelMockL1ToL2Messages, l1ToL2Messages), - ).rejects.toThrow(); + await expect(builder.buildL2Block(globalVariables, txs, l1ToL2Messages)).rejects.toThrow(); }); }); @@ -377,12 +368,7 @@ describe('sequencer/solo_block_builder', () => { ...(await Promise.all(times(totalCount - bloatedCount, makeEmptyProcessedTx))), ]; - const [l2Block] = await builder.buildL2Block( - globalVariables, - txs, - newModelMockL1ToL2Messages, - mockL1ToL2Messages, - ); + const [l2Block] = await builder.buildL2Block(globalVariables, txs, mockL1ToL2Messages); expect(l2Block.number).toEqual(blockNumber); await updateExpectedTreesFromTxs(txs); @@ -406,12 +392,7 @@ describe('sequencer/solo_block_builder', () => { makeEmptyProcessedTx(), ]); - const [l2Block] = await builder.buildL2Block( - globalVariables, - txs, - newModelMockL1ToL2Messages, - mockL1ToL2Messages, - ); + const [l2Block] = await builder.buildL2Block(globalVariables, txs, mockL1ToL2Messages); expect(l2Block.number).toEqual(blockNumber); }, 30_000); @@ -425,7 +406,7 @@ describe('sequencer/solo_block_builder', () => { const l1ToL2Messages = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 1 + 0x400).map(fr); - const [l2Block] = await builder.buildL2Block(globalVariables, txs, newModelMockL1ToL2Messages, l1ToL2Messages); + const [l2Block] = await builder.buildL2Block(globalVariables, txs, l1ToL2Messages); expect(l2Block.number).toEqual(blockNumber); }, 200_000); @@ -461,12 +442,7 @@ describe('sequencer/solo_block_builder', () => { NULLIFIER_SUBTREE_HEIGHT, ); - const [l2Block] = await builder.buildL2Block( - globalVariables, - txs, - newModelMockL1ToL2Messages, - mockL1ToL2Messages, - ); + const [l2Block] = await builder.buildL2Block(globalVariables, txs, mockL1ToL2Messages); expect(l2Block.number).toEqual(blockNumber); }, 20000); diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts index 4b9ca64435c..93530000952 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts @@ -89,32 +89,28 @@ export class SoloBlockBuilder implements BlockBuilder { * Builds an L2 block with the given number containing the given txs, updating state trees. * @param globalVariables - Global variables to be used in the block. * @param txs - Processed transactions to include in the block. - * @param newModelL1ToL2Messages - L1 to L2 messages emitted by the new inbox. - * @param newL1ToL2Messages - L1 to L2 messages to be part of the block. + * @param l1ToL2Messages - L1 to L2 messages to be part of the block. * @param timestamp - Timestamp of the block. * @returns The new L2 block and a correctness proof as returned by the root rollup circuit. */ public async buildL2Block( globalVariables: GlobalVariables, txs: ProcessedTx[], - newModelL1ToL2Messages: Fr[], // TODO(#4492): Rename this when purging the old inbox - newL1ToL2Messages: Fr[], + l1ToL2Messages: Fr[], ): Promise<[L2Block, Proof]> { // Check txs are good for processing by checking if all the tree snapshots in header are non-empty this.validateTxs(txs); + // We pad the messages as the circuits expect that. + const l1ToL2MessagesPadded = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); + // We fill the tx batch with empty txs, we process only one tx at a time for now - const [circuitsOutput, proof] = await this.runCircuits( - globalVariables, - txs, - newModelL1ToL2Messages, - newL1ToL2Messages, - ); + const [circuitsOutput, proof] = await this.runCircuits(globalVariables, txs, l1ToL2MessagesPadded); // Collect all new nullifiers, commitments, and contracts from all txs in this block const txEffects: TxEffect[] = txs.map(tx => toTxEffect(tx)); - const blockBody = new Body(padArrayEnd(newL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP), txEffects); + const blockBody = new Body(l1ToL2MessagesPadded, txEffects); const l2Block = L2Block.fromFields({ archive: circuitsOutput.archive, @@ -160,8 +156,7 @@ export class SoloBlockBuilder implements BlockBuilder { protected async runCircuits( globalVariables: GlobalVariables, txs: ProcessedTx[], - newModelL1ToL2Messages: Fr[], // TODO(#4492): Rename this when purging the old inbox - newL1ToL2Messages: Fr[], // TODO(#4492): Nuke this when purging the old inbox + l1ToL2Messages: Tuple, ): Promise<[RootRollupPublicInputs, Proof]> { // Check that the length of the array of txs is a power of two // See https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 @@ -170,16 +165,13 @@ export class SoloBlockBuilder implements BlockBuilder { } // BASE PARITY CIRCUIT (run in parallel) + // Note: In the future we will want to cache the results of empty base and root parity circuits so that we don't + // have to run them. (It will most likely be quite common that some base parity circuits will be "empty") let baseParityInputs: BaseParityInputs[] = []; let elapsedBaseParityOutputsPromise: Promise<[number, RootParityInput[]]>; { - const newModelL1ToL2MessagesTuple = padArrayEnd( - newModelL1ToL2Messages, - Fr.ZERO, - NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, - ); baseParityInputs = Array.from({ length: NUM_BASE_PARITY_PER_ROOT_PARITY }, (_, i) => - BaseParityInputs.fromSlice(newModelL1ToL2MessagesTuple, i), + BaseParityInputs.fromSlice(l1ToL2Messages, i), ); const baseParityOutputs: Promise[] = []; @@ -189,9 +181,6 @@ export class SoloBlockBuilder implements BlockBuilder { elapsedBaseParityOutputsPromise = elapsed(() => Promise.all(baseParityOutputs)); } - // padArrayEnd throws if the array is already full. Otherwise it pads till we reach the required size - const newL1ToL2MessagesTuple = padArrayEnd(newL1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); - // BASE ROLLUP CIRCUIT (run in parallel) let elapsedBaseRollupOutputsPromise: Promise<[number, [BaseOrMergeRollupPublicInputs, Proof][]]>; const baseRollupInputs: BaseRollupInputs[] = []; @@ -302,7 +291,7 @@ export class SoloBlockBuilder implements BlockBuilder { outputSize: rootParityOutput.toBuffer().length, } satisfies CircuitSimulationStats); - return this.rootRollupCircuit(mergeOutputLeft, mergeOutputRight, rootParityOutput, newL1ToL2MessagesTuple); + return this.rootRollupCircuit(mergeOutputLeft, mergeOutputRight, rootParityOutput, l1ToL2Messages); } protected async baseParityCircuit(inputs: BaseParityInputs): Promise { @@ -365,15 +354,15 @@ export class SoloBlockBuilder implements BlockBuilder { left: [BaseOrMergeRollupPublicInputs, Proof], right: [BaseOrMergeRollupPublicInputs, Proof], l1ToL2Roots: RootParityInput, - newL1ToL2Messages: Tuple, + l1ToL2Messages: Tuple, ): Promise<[RootRollupPublicInputs, Proof]> { this.debug(`Running root rollup circuit`); - const rootInput = await this.getRootRollupInput(...left, ...right, l1ToL2Roots, newL1ToL2Messages); + const rootInput = await this.getRootRollupInput(...left, ...right, l1ToL2Roots, l1ToL2Messages); - // Update the local trees to include the new l1 to l2 messages + // Update the local trees to include the l1 to l2 messages await this.db.appendLeaves( MerkleTreeId.L1_TO_L2_MESSAGE_TREE, - newL1ToL2Messages.map(m => m.toBuffer()), + l1ToL2Messages.map(m => m.toBuffer()), ); // Simulate and get proof for the root circuit diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 589d745d554..8e151a0fc6a 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -73,8 +73,7 @@ describe('sequencer', () => { }); l1ToL2MessageSource = mock({ - getNewL1ToL2Messages: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), - getPendingL1ToL2EntryKeys: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), + getL1ToL2Messages: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), getBlockNumber: () => Promise.resolve(lastBlockNumber), }); @@ -112,7 +111,6 @@ describe('sequencer', () => { new GlobalVariables(chainId, version, new Fr(lastBlockNumber + 1), Fr.ZERO, coinbase, feeRecipient), expectedTxHashes.map(hash => expect.objectContaining({ hash })), Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), - Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); }); @@ -150,7 +148,6 @@ describe('sequencer', () => { new GlobalVariables(chainId, version, new Fr(lastBlockNumber + 1), Fr.ZERO, coinbase, feeRecipient), expectedTxHashes.map(hash => expect.objectContaining({ hash })), Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), - Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); expect(p2p.deleteTxs).toHaveBeenCalledWith([doubleSpendTx.getTxHash()]); @@ -184,7 +181,6 @@ describe('sequencer', () => { new GlobalVariables(chainId, version, new Fr(lastBlockNumber + 1), Fr.ZERO, coinbase, feeRecipient), expectedTxHashes.map(hash => expect.objectContaining({ hash })), Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), - Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); expect(p2p.deleteTxs).toHaveBeenCalledWith([invalidChainTx.getTxHash()]); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index f22ec684ff7..5e58ba6fd83 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -202,12 +202,10 @@ export class Sequencer { await assertBlockHeight(); - const newModelL1ToL2Messages = await this.l1ToL2MessageSource.getNewL1ToL2Messages(BigInt(newBlockNumber)); - // Get l1 to l2 messages from the contract this.log('Requesting L1 to L2 messages from contract'); - const l1ToL2Messages = await this.getPendingL1ToL2EntryKeys(); - this.log('Successfully retrieved L1 to L2 messages from contract'); + const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(newBlockNumber)); + this.log(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`); // Build the new block by running the rollup circuits this.log(`Assembling block with txs ${processedValidTxs.map(tx => tx.hash).join(', ')}`); @@ -216,7 +214,7 @@ export class Sequencer { const emptyTx = processor.makeEmptyProcessedTx(); const [rollupCircuitsDuration, block] = await elapsed(() => - this.buildBlock(processedValidTxs, newModelL1ToL2Messages, l1ToL2Messages, emptyTx, newGlobalVariables), + this.buildBlock(processedValidTxs, l1ToL2Messages, emptyTx, newGlobalVariables), ); this.log(`Assembled block ${block.number}`, { @@ -314,16 +312,14 @@ export class Sequencer { /** * Pads the set of txs to a power of two and assembles a block by calling the block builder. * @param txs - Processed txs to include in the next block. - * @param newModelL1ToL2Messages - L1 to L2 messages emitted by the new inbox. - * @param newL1ToL2Messages - L1 to L2 messages to be part of the block. + * @param l1ToL2Messages - L1 to L2 messages to be part of the block. * @param emptyTx - Empty tx to repeat at the end of the block to pad to a power of two. * @param globalVariables - Global variables to use in the block. * @returns The new block. */ protected async buildBlock( txs: ProcessedTx[], - newModelL1ToL2Messages: Fr[], // TODO(#4492): Rename this when purging the old inbox - newL1ToL2Messages: Fr[], // TODO(#4492): Nuke this when purging the old inbox + l1ToL2Messages: Fr[], emptyTx: ProcessedTx, globalVariables: GlobalVariables, ) { @@ -332,24 +328,10 @@ export class Sequencer { const emptyTxCount = txsTargetSize - txs.length; const allTxs = [...txs, ...times(emptyTxCount, () => emptyTx)]; - this.log(`Building block ${globalVariables.blockNumber}`); - - const [block] = await this.blockBuilder.buildL2Block( - globalVariables, - allTxs, - newModelL1ToL2Messages, - newL1ToL2Messages, - ); - return block; - } + this.log(`Building block ${globalVariables.blockNumber.toBigInt()}`); - /** - * Calls the archiver to pull upto `NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP` entry keys - * (archiver returns the top messages sorted by fees) - * @returns An array of L1 to L2 messages' entryKeys - */ - protected async getPendingL1ToL2EntryKeys(): Promise { - return await this.l1ToL2MessageSource.getPendingL1ToL2EntryKeys(); + const [block] = await this.blockBuilder.buildL2Block(globalVariables, allTxs, l1ToL2Messages); + return block; } /** diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index c941386e3c8..f2a0f1e8ccf 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -48,7 +48,7 @@ import { import { jest } from '@jest/globals'; import { MockProxy, mock } from 'jest-mock-extended'; -import { getFunctionSelector } from 'viem'; +import { toFunctionSelector } from 'viem'; import { KeyPair, MessageLoadOracleInputs } from '../acvm/index.js'; import { buildL1ToL2Message } from '../test/utils.js'; @@ -525,7 +525,6 @@ describe('Private Execution test suite', () => { describe('L1 to L2', () => { const artifact = getFunctionArtifact(TestContractArtifact, 'consume_mint_private_message'); - const canceller = EthAddress.random(); let bridgedAmount = 100n; const secretHashForRedeemingNotes = new Fr(2n); @@ -548,19 +547,14 @@ describe('Private Execution test suite', () => { const computePreimage = () => buildL1ToL2Message( - getFunctionSelector('mint_private(bytes32,uint256,address)').substring(2), - [secretHashForRedeemingNotes, new Fr(bridgedAmount), canceller.toField()], + toFunctionSelector('mint_private(bytes32,uint256)').substring(2), + [secretHashForRedeemingNotes, new Fr(bridgedAmount)], crossChainMsgRecipient ?? contractAddress, secretForL1ToL2MessageConsumption, ); const computeArgs = () => - encodeArguments(artifact, [ - secretHashForRedeemingNotes, - bridgedAmount, - canceller.toField(), - secretForL1ToL2MessageConsumption, - ]); + encodeArguments(artifact, [secretHashForRedeemingNotes, bridgedAmount, secretForL1ToL2MessageConsumption]); const mockOracles = async (updateHeader = true) => { const tree = await insertLeaves([preimage.hash()], 'l1ToL2Messages'); diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index e6ce6a35e34..01a2cbe946a 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -27,7 +27,7 @@ import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { MockProxy, mock } from 'jest-mock-extended'; import { type MemDown, default as memdown } from 'memdown'; -import { getFunctionSelector } from 'viem'; +import { toFunctionSelector } from 'viem'; import { MessageLoadOracleInputs } from '../index.js'; import { buildL1ToL2Message } from '../test/utils.js'; @@ -416,7 +416,6 @@ describe('ACIR public execution simulator', () => { describe('L1 to L2 messages', () => { const mintPublicArtifact = TestContractArtifact.functions.find(f => f.name === 'consume_mint_public_message')!; - const canceller = EthAddress.random(); const tokenRecipient = AztecAddress.random(); let bridgedAmount = 20n; let secret = new Fr(1); @@ -440,13 +439,13 @@ describe('ACIR public execution simulator', () => { const computePreImage = () => buildL1ToL2Message( - getFunctionSelector('mint_public(bytes32,uint256,address)').substring(2), - [tokenRecipient.toField(), new Fr(bridgedAmount), canceller.toField()], + toFunctionSelector('mint_public(bytes32,uint256)').substring(2), + [tokenRecipient.toField(), new Fr(bridgedAmount)], crossChainMsgRecipient ?? contractAddress, secret, ); - const computeArgs = () => encodeArguments(mintPublicArtifact, [tokenRecipient, bridgedAmount, canceller, secret]); + const computeArgs = () => encodeArguments(mintPublicArtifact, [tokenRecipient, bridgedAmount, secret]); const computeCallContext = () => CallContext.from({ diff --git a/yarn-project/simulator/src/test/utils.ts b/yarn-project/simulator/src/test/utils.ts index 5d9e510e2bc..c9f40a83af6 100644 --- a/yarn-project/simulator/src/test/utils.ts +++ b/yarn-project/simulator/src/test/utils.ts @@ -27,12 +27,5 @@ export const buildL1ToL2Message = ( // Eventually the kernel will need to prove the kernel portal pair exists within the contract tree, // EthAddress.random() will need to be replaced when this happens - return new L1ToL2Message( - new L1Actor(EthAddress.random(), 1), - new L2Actor(targetContract, 1), - content, - secretHash, - 2 ** 32 - 1, - 0, - ); + return new L1ToL2Message(new L1Actor(EthAddress.random(), 1), new L2Actor(targetContract, 1), content, secretHash); };