diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml index 320b0c219..5637607f5 100644 --- a/.github/workflows/contract-tests.yml +++ b/.github/workflows/contract-tests.yml @@ -33,7 +33,7 @@ jobs: - name: Install packages run: yarn - - name: Run unit tests + - name: Build run: forge test tests: name: Contract tests @@ -44,6 +44,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly - name: Setup nodejs uses: actions/setup-node@v2 @@ -67,7 +74,7 @@ jobs: run: yarn lint:test - name: Build - run: yarn build + run: yarn build:all - name: Run tests run: yarn hardhat --network hardhat test test/contract/*.spec.ts @@ -94,3 +101,37 @@ jobs: files: ./contracts/coverage.json verbose: false token: ${{ secrets.CODECOV_TOKEN }} + test-4844: + name: 4844 tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - uses: OffchainLabs/actions/run-nitro-test-node@test-node-args + with: + nitro-testnode-ref: deneb-integration + args: --pos + no-token-bridge: true + + - name: Setup nodejs + uses: actions/setup-node@v2 + with: + node-version: '18' + cache: 'yarn' + cache-dependency-path: '**/yarn.lock' + + - name: Install dependencies + run: yarn install + + - name: Build + run: yarn build:all + + - name: Test 4844 + run: yarn test:4844 diff --git a/.prettierrc.js b/.prettierrc.js index b08bd49d3..5392936bc 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -14,7 +14,7 @@ module.exports = { printWidth: 100, singleQuote: false, bracketSpacing: false, - compiler: '0.8.6', + compiler: '0.8.9', }, }, ], diff --git a/.solhint.json b/.solhint.json index 3ab96cfe0..ee37a04cf 100644 --- a/.solhint.json +++ b/.solhint.json @@ -11,7 +11,7 @@ "no-empty-blocks": "off", "reason-string": ["warn", { "maxLength": 128 }], "not-rely-on-time": "off", - "max-states-count": ["warn", 30], + "max-states-count": ["warn", 40], "no-inline-assembly": "off" }, "plugins": ["prettier"] diff --git a/LICENSE.md b/LICENSE.md index 1aee318fa..a4e5a7f61 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -31,6 +31,7 @@ Additional Use Grant: You may use the Licensed Work in a production environment Covered Arbitrum Chain. + Change Date: Dec 31, 2028 Change License: Apache License Version 2.0 diff --git a/README.md b/README.md index 9d4812d7f..4ddbf31a7 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,12 @@ yarn build Nitro is currently licensed under a [Business Source License](./LICENSE), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains. -The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova. +The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova. -For those that prefer to deploy the Nitro software either directly on Ethereum (i.e. an L2) or have it settle to another Layer-2 on top of Ethereum, the [Arbitrum Expansion Program (the "AEP")](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf) was recently established. The AEP allows for the permissionless deployment in the aforementioned fashion provided that 10% of net revenue is contributed back to the Arbitrum community in accordance with the requirements of the AEP. +For those that prefer to deploy the Nitro software either directly on Ethereum (i.e. an L2) or have it settle to another Layer-2 on top of Ethereum, the [Arbitrum Expansion Program (the "AEP")](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf) was recently established. The AEP allows for the permissionless deployment in the aforementioned fashion provided that 10% of net revenue is contributed back to the Arbitrum community in accordance with the requirements of the AEP. ## Contact Discord - [Arbitrum](https://discord.com/invite/5KE54JwyTs) Twitter: [Arbitrum](https://twitter.com/arbitrum) - - diff --git a/deploy/SequencerInbox.js b/deploy/SequencerInbox.js index b7cff1992..d90135a9c 100644 --- a/deploy/SequencerInbox.js +++ b/deploy/SequencerInbox.js @@ -3,6 +3,9 @@ module.exports = async hre => { const { deploy } = deployments const { deployer } = await getNamedAccounts() + const blobBasefeeReader = await ethers.getContract('BlobBasefeeReader') + const dataHashReader = await ethers.getContract('DataHashReader') + await deploy('SequencerInbox', { from: deployer, args: [117964] }) } diff --git a/deploy/SequencerInboxStubCreator.js b/deploy/SequencerInboxStubCreator.js index 4748a9244..e61a227ca 100644 --- a/deploy/SequencerInboxStubCreator.js +++ b/deploy/SequencerInboxStubCreator.js @@ -1,9 +1,14 @@ +import { Toolkit4844 } from '../test/contract/toolkit4844' + module.exports = async hre => { - const { deployments, getNamedAccounts, ethers } = hre + const { deployments, getSigners, getNamedAccounts, ethers } = hre const { deploy } = deployments const { deployer } = await getNamedAccounts() const bridge = await ethers.getContract('BridgeStub') + const reader4844 = await Toolkit4844.deployReader4844( + await ethers.getSigner(deployer) + ) const maxTime = { delayBlocks: 10000, futureBlocks: 10000, @@ -12,7 +17,14 @@ module.exports = async hre => { } await deploy('SequencerInboxStub', { from: deployer, - args: [bridge.address, deployer, maxTime, 117964], + args: [ + bridge.address, + deployer, + maxTime, + 117964, + reader4844.address, + false, + ], }) } diff --git a/foundry.toml b/foundry.toml index 2512e19b3..774b6a24f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,12 +1,19 @@ [profile.default] -src = 'src' +src = 'src/' out = 'out' libs = ['node_modules', 'lib'] test = 'test/foundry' -cache_path = 'forge-cache' +cache_path = 'forge-cache/sol' optimizer = true optimizer_runs = 20000 via_ir = false +solc_version = '0.8.9' + +[profile.yul] +src = 'yul' +out = 'out/yul' +libs = ['node_modules', 'lib'] +cache_path = 'forge-cache/yul' [fmt] number_underscore = 'thousands' diff --git a/hardhat.config.ts b/hardhat.config.ts index 56aa91636..401a29b30 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -6,6 +6,7 @@ import '@typechain/hardhat' import 'solidity-coverage' import 'hardhat-gas-reporter' import 'hardhat-ignore-warnings' +// import '@tovarishfin/hardhat-yul'; import dotenv from 'dotenv' dotenv.config() diff --git a/package.json b/package.json index 1da14a92e..6fd390805 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arbitrum/nitro-contracts", - "version": "1.1.1", + "version": "1.2.0", "description": "Layer 2 precompiles and rollup for Arbitrum Nitro", "author": "Offchain Labs, Inc.", "license": "BUSL-1.1", @@ -18,13 +18,19 @@ }, "scripts": { "prepublishOnly": "hardhat clean && hardhat compile", + "build:all": "yarn build && yarn build:forge", "build": "hardhat compile", + "build:forge:sol": "forge build --skip *.yul", + "build:forge:yul": "FOUNDRY_PROFILE=yul forge build --skip *.sol", + "build:forge": "yarn build:forge:sol && yarn build:forge:yul", "lint:test": "eslint ./test", "solhint": "solhint -f table src/**/*.sol", "prettier:solidity": "prettier --write src/**/*.sol", - "format": "prettier './**/*.{js,json,md,ts,yml,sol}' --write && yarn run lint:test --fix", + "format": "prettier './**/*.{js,json,ts,yml,sol}' --write && yarn run lint:test --fix", "build:0.6": "INTERFACE_TESTER_SOLC_VERSION=0.6.9 yarn run build", "build:0.7": "INTERFACE_TESTER_SOLC_VERSION=0.7.0 yarn run build", + "test": "DISABLE_GAS_REPORTER=true hardhat --network hardhat test test/contract/*.spec.ts", + "test:4844": "DISABLE_GAS_REPORTER=true hardhat --network hardhat test test/contract/*.spec.4844.ts", "test:compatibility": "yarn run build:0.6 && yarn run build:0.7", "test:storage": "./test/storage/test.bash", "test:signatures": "./test/sginatures/test-sigs.bash", @@ -47,6 +53,7 @@ "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13", "@nomiclabs/hardhat-etherscan": "^3.1.0", "@nomiclabs/hardhat-waffle": "^2.0.1", + "@tovarishfin/hardhat-yul": "^3.0.5", "@typechain/ethers-v5": "^10.0.0", "@typechain/hardhat": "^6.0.0", "@types/chai": "^4.3.0", diff --git a/scripts/deployment.ts b/scripts/deployment.ts index 93c091142..01162c552 100644 --- a/scripts/deployment.ts +++ b/scripts/deployment.ts @@ -81,6 +81,7 @@ async function deployAllContracts( const ethBridge = await deployContract('Bridge', signer, []) const ethSequencerInbox = await deployContract('SequencerInbox', signer, [ maxDataSize, + false, ]) const ethInbox = await deployContract('Inbox', signer, [maxDataSize]) const ethRollupEventInbox = await deployContract( @@ -91,7 +92,10 @@ async function deployAllContracts( const ethOutbox = await deployContract('Outbox', signer, []) const erc20Bridge = await deployContract('ERC20Bridge', signer, []) - const erc20SequencerInbox = ethSequencerInbox + const erc20SequencerInbox = await deployContract('SequencerInbox', signer, [ + maxDataSize, + true, + ]) const erc20Inbox = await deployContract('ERC20Inbox', signer, [maxDataSize]) const erc20RollupEventInbox = await deployContract( 'ERC20RollupEventInbox', diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 26d060f8b..82e40cb5e 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -85,15 +85,11 @@ export async function createRollup(feeToken?: string) { maxDataSize: maxDataSize, nativeToken: feeToken, deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FER_PER_GAS + maxFeePerGasForRetryables: MAX_FER_PER_GAS, } - const createRollupTx = await rollupCreator.createRollup( - deployParams, - { - value: feeCost, - } - - ) + const createRollupTx = await rollupCreator.createRollup(deployParams, { + value: feeCost, + }) const createRollupReceipt = await createRollupTx.wait() const rollupCreatedEvent = createRollupReceipt.events?.find( diff --git a/src/bridge/GasRefunder.sol b/src/bridge/GasRefunder.sol index 82666a3cc..5f85e9f2c 100644 --- a/src/bridge/GasRefunder.sol +++ b/src/bridge/GasRefunder.sol @@ -165,6 +165,7 @@ contract GasRefunder is IGasRefunder, Ownable { function withdraw(address payable destination, uint256 amount) external onlyOwner { // It's expected that destination is an EOA + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = destination.call{value: amount}(""); require(success, "WITHDRAW_FAILED"); emit Withdrawn(msg.sender, destination, amount); @@ -249,6 +250,7 @@ contract GasRefunder is IGasRefunder, Ownable { } // It's expected that refundee is an EOA + // solhint-disable-next-line avoid-low-level-calls (success, ) = refundee.call{value: refundAmount}(""); emit RefundedGasCosts(refundee, msg.sender, success, gasUsed, estGasPrice, refundAmount); } diff --git a/src/bridge/IBridge.sol b/src/bridge/IBridge.sol index 22388b4bd..1137fcd36 100644 --- a/src/bridge/IBridge.sol +++ b/src/bridge/IBridge.sol @@ -8,6 +8,28 @@ pragma solidity >=0.6.9 <0.9.0; import "./IOwnable.sol"; interface IBridge { + /// @dev This is an instruction to offchain readers to inform them where to look + /// for sequencer inbox batch data. This is not the type of data (eg. das, brotli encoded, or blob versioned hash) + /// and this enum is not used in the state transition function, rather it informs an offchain + /// reader where to find the data so that they can supply it to the replay binary + enum BatchDataLocation { + /// @notice The data can be found in the transaction call data + TxInput, + /// @notice The data can be found in an event emitted during the transaction + SeparateBatchEvent, + /// @notice This batch contains no data + NoData, + /// @notice The data can be found in the 4844 data blobs on this transaction + Blob + } + + struct TimeBounds { + uint64 minTimestamp; + uint64 maxTimestamp; + uint64 minBlockNumber; + uint64 maxBlockNumber; + } + event MessageDelivered( uint256 indexed messageIndex, bytes32 indexed beforeInboxAcc, diff --git a/src/bridge/ISequencerInbox.sol b/src/bridge/ISequencerInbox.sol index 7d66befc1..45f2028ee 100644 --- a/src/bridge/ISequencerInbox.sol +++ b/src/bridge/ISequencerInbox.sol @@ -12,23 +12,10 @@ import "./IBridge.sol"; interface ISequencerInbox is IDelayedMessageProvider { struct MaxTimeVariation { - uint256 delayBlocks; - uint256 futureBlocks; - uint256 delaySeconds; - uint256 futureSeconds; - } - - struct TimeBounds { - uint64 minTimestamp; - uint64 maxTimestamp; - uint64 minBlockNumber; - uint64 maxBlockNumber; - } - - enum BatchDataLocation { - TxInput, - SeparateBatchEvent, - NoData + uint64 delayBlocks; + uint64 futureBlocks; + uint64 delaySeconds; + uint64 futureSeconds; } event SequencerBatchDelivered( @@ -37,8 +24,8 @@ interface ISequencerInbox is IDelayedMessageProvider { bytes32 indexed afterAcc, bytes32 delayedAcc, uint256 afterDelayedMessagesRead, - TimeBounds timeBounds, - BatchDataLocation dataLocation + IBridge.TimeBounds timeBounds, + IBridge.BatchDataLocation dataLocation ); event OwnerFunctionCalled(uint256 indexed id); @@ -61,10 +48,41 @@ interface ISequencerInbox is IDelayedMessageProvider { function HEADER_LENGTH() external view returns (uint256); /// @dev If the first batch data byte after the header has this bit set, - /// the sequencer inbox has authenticated the data. Currently not used. + /// the sequencer inbox has authenticated the data. Currently only used for 4844 blob support. + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go // solhint-disable-next-line func-name-mixedcase function DATA_AUTHENTICATED_FLAG() external view returns (bytes1); + /// @dev If the first data byte after the header has this bit set, + /// then the batch data is to be found in 4844 data blobs + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go + // solhint-disable-next-line func-name-mixedcase + function DATA_BLOB_HEADER_FLAG() external view returns (bytes1); + + /// @dev If the first data byte after the header has this bit set, + /// then the batch data is a das message + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go + // solhint-disable-next-line func-name-mixedcase + function DAS_MESSAGE_HEADER_FLAG() external view returns (bytes1); + + /// @dev If the first data byte after the header has this bit set, + /// then the batch data is a das message that employs a merklesization strategy + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go + // solhint-disable-next-line func-name-mixedcase + function TREE_DAS_MESSAGE_HEADER_FLAG() external view returns (bytes1); + + /// @dev If the first data byte after the header has this bit set, + /// then the batch data has been brotli compressed + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go + // solhint-disable-next-line func-name-mixedcase + function BROTLI_MESSAGE_HEADER_FLAG() external view returns (bytes1); + + /// @dev If the first data byte after the header has this bit set, + /// then the batch data uses a zero heavy encoding + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go + // solhint-disable-next-line func-name-mixedcase + function ZERO_HEAVY_MESSAGE_HEADER_FLAG() external view returns (bytes1); + function rollup() external view returns (IOwnable); function isBatchPoster(address) external view returns (bool); @@ -73,19 +91,24 @@ interface ISequencerInbox is IDelayedMessageProvider { function maxDataSize() external view returns (uint256); + /// @notice The batch poster manager has the ability to change the batch poster addresses + /// This enables the batch poster to do key rotation + function batchPosterManager() external view returns (address); + struct DasKeySetInfo { bool isValidKeyset; uint64 creationBlock; } + /// @dev returns 4 uint256 to be compatible with older version function maxTimeVariation() external view returns ( - uint256, - uint256, - uint256, - uint256 + uint256 delayBlocks, + uint256 futureBlocks, + uint256 delaySeconds, + uint256 futureSeconds ); function dasKeySetInfo(bytes32) external view returns (bool, uint64); @@ -130,6 +153,15 @@ interface ISequencerInbox is IDelayedMessageProvider { IGasRefunder gasRefunder ) external; + function addSequencerL2BatchFromOrigin( + uint256 sequenceNumber, + bytes calldata data, + uint256 afterDelayedMessagesRead, + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount + ) external; + function addSequencerL2Batch( uint256 sequenceNumber, bytes calldata data, @@ -139,6 +171,14 @@ interface ISequencerInbox is IDelayedMessageProvider { uint256 newMessageCount ) external; + function addSequencerL2BatchFromBlobs( + uint256 sequenceNumber, + uint256 afterDelayedMessagesRead, + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount + ) external; + // ---------- onlyRollupOrOwner functions ---------- /** @@ -174,9 +214,16 @@ interface ISequencerInbox is IDelayedMessageProvider { */ function setIsSequencer(address addr, bool isSequencer_) external; + /** + * @notice Updates the batch poster manager, the address which has the ability to rotate batch poster keys + * @param newBatchPosterManager The new batch poster manager to be set + */ + function setBatchPosterManager(address newBatchPosterManager) external; + + /// @notice Allows the rollup owner to sync the rollup address + function updateRollupAddress() external; + // ---------- initializer ---------- function initialize(IBridge bridge_, MaxTimeVariation calldata maxTimeVariation_) external; - - function updateRollupAddress() external; } diff --git a/src/bridge/SequencerInbox.sol b/src/bridge/SequencerInbox.sol index 51d511e24..58249dae4 100644 --- a/src/bridge/SequencerInbox.sol +++ b/src/bridge/SequencerInbox.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import { AlreadyInit, HadZeroInit, + BadPostUpgradeInit, NotOrigin, DataTooLarge, NotRollup, @@ -21,7 +22,18 @@ import { AlreadyValidDASKeyset, NoSuchKeyset, NotForked, - RollupNotChanged + NotBatchPosterManager, + RollupNotChanged, + DataBlobsNotSupported, + InitParamZero, + MissingDataHashes, + InvalidBlobMetadata, + NotOwner, + RollupNotChanged, + EmptyBatchData, + InvalidHeaderFlag, + NativeTokenMismatch, + Deprecated } from "../libraries/Error.sol"; import "./IBridge.sol"; import "./IInboxBase.sol"; @@ -30,18 +42,21 @@ import "../rollup/IRollupLogic.sol"; import "./Messages.sol"; import "../precompiles/ArbGasInfo.sol"; import "../precompiles/ArbSys.sol"; +import "../libraries/IReader4844.sol"; import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol"; -import {GasRefundEnabled, IGasRefunder} from "../libraries/IGasRefunder.sol"; import "../libraries/DelegateCallAware.sol"; +import {IGasRefunder} from "../libraries/IGasRefunder.sol"; +import {GasRefundEnabled} from "../libraries/GasRefundEnabled.sol"; import "../libraries/ArbitrumChecker.sol"; +import {IERC20Bridge} from "./IERC20Bridge.sol"; /** - * @title Accepts batches from the sequencer and adds them to the rollup inbox. + * @title Accepts batches from the sequencer and adds them to the rollup inbox. * @notice Contains the inbox accumulator which is the ordering of all data and transactions to be processed by the rollup. - * As part of submitting a batch the sequencer is also expected to include items enqueued - * in the delayed inbox (Bridge.sol). If items in the delayed inbox are not included by a - * sequencer within a time limit they can be force included into the rollup inbox by anyone. + * As part of submitting a batch the sequencer is also expected to include items enqueued + * in the delayed inbox (Bridge.sol). If items in the delayed inbox are not included by a + * sequencer within a time limit they can be force included into the rollup inbox by anyone. */ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox { uint256 public totalDelayedMessagesRead; @@ -54,42 +69,142 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox /// @inheritdoc ISequencerInbox bytes1 public constant DATA_AUTHENTICATED_FLAG = 0x40; + /// @inheritdoc ISequencerInbox + bytes1 public constant DATA_BLOB_HEADER_FLAG = DATA_AUTHENTICATED_FLAG | 0x10; + + /// @inheritdoc ISequencerInbox + bytes1 public constant DAS_MESSAGE_HEADER_FLAG = 0x80; + + /// @inheritdoc ISequencerInbox + bytes1 public constant TREE_DAS_MESSAGE_HEADER_FLAG = 0x08; + + /// @inheritdoc ISequencerInbox + bytes1 public constant BROTLI_MESSAGE_HEADER_FLAG = 0x00; + + /// @inheritdoc ISequencerInbox + bytes1 public constant ZERO_HEAVY_MESSAGE_HEADER_FLAG = 0x20; + + // GAS_PER_BLOB from EIP-4844 + uint256 internal constant GAS_PER_BLOB = 1 << 17; + IOwnable public rollup; + mapping(address => bool) public isBatchPoster; - ISequencerInbox.MaxTimeVariation public maxTimeVariation; + + // we previously stored the max time variation in a (uint,uint,uint,uint) struct here + // solhint-disable-next-line var-name-mixedcase + uint256[4] private __LEGACY_MAX_TIME_VARIATION; mapping(bytes32 => DasKeySetInfo) public dasKeySetInfo; modifier onlyRollupOwner() { - if (msg.sender != rollup.owner()) revert NotOwner(msg.sender, address(rollup)); + if (msg.sender != rollup.owner()) revert NotOwner(msg.sender, rollup.owner()); + _; + } + + modifier onlyRollupOwnerOrBatchPosterManager() { + if (msg.sender != rollup.owner() && msg.sender != batchPosterManager) { + revert NotBatchPosterManager(msg.sender); + } _; } mapping(address => bool) public isSequencer; + IReader4844 public immutable reader4844; + + // see ISequencerInbox.MaxTimeVariation + uint64 internal delayBlocks; + uint64 internal futureBlocks; + uint64 internal delaySeconds; + uint64 internal futureSeconds; + + /// @inheritdoc ISequencerInbox + address public batchPosterManager; // On L1 this should be set to 117964: 90% of Geth's 128KB tx size limit, leaving ~13KB for proving uint256 public immutable maxDataSize; uint256 internal immutable deployTimeChainId = block.chainid; // If the chain this SequencerInbox is deployed on is an Arbitrum chain. bool internal immutable hostChainIsArbitrum = ArbitrumChecker.runningOnArbitrum(); - - constructor(uint256 _maxDataSize) { + // True if the chain this SequencerInbox is deployed on uses custom fee token + bool public immutable isUsingFeeToken; + + constructor( + uint256 _maxDataSize, + IReader4844 reader4844_, + bool _isUsingFeeToken + ) { maxDataSize = _maxDataSize; + if (hostChainIsArbitrum) { + if (reader4844_ != IReader4844(address(0))) revert DataBlobsNotSupported(); + } else { + if (reader4844_ == IReader4844(address(0))) revert InitParamZero("Reader4844"); + } + reader4844 = reader4844_; + isUsingFeeToken = _isUsingFeeToken; } function _chainIdChanged() internal view returns (bool) { return deployTimeChainId != block.chainid; } + function postUpgradeInit() external onlyDelegated onlyProxyOwner { + // Assuming we would not upgrade from a version that have MaxTimeVariation all set to zero + // If that is the case, postUpgradeInit do not need to be called + if ( + __LEGACY_MAX_TIME_VARIATION[0] == 0 && + __LEGACY_MAX_TIME_VARIATION[1] == 0 && + __LEGACY_MAX_TIME_VARIATION[2] == 0 && + __LEGACY_MAX_TIME_VARIATION[3] == 0 + ) { + revert AlreadyInit(); + } + + if ( + __LEGACY_MAX_TIME_VARIATION[0] > type(uint64).max || + __LEGACY_MAX_TIME_VARIATION[1] > type(uint64).max || + __LEGACY_MAX_TIME_VARIATION[2] > type(uint64).max || + __LEGACY_MAX_TIME_VARIATION[3] > type(uint64).max + ) { + revert BadPostUpgradeInit(); + } + + delayBlocks = uint64(__LEGACY_MAX_TIME_VARIATION[0]); + futureBlocks = uint64(__LEGACY_MAX_TIME_VARIATION[1]); + delaySeconds = uint64(__LEGACY_MAX_TIME_VARIATION[2]); + futureSeconds = uint64(__LEGACY_MAX_TIME_VARIATION[3]); + + __LEGACY_MAX_TIME_VARIATION[0] = 0; + __LEGACY_MAX_TIME_VARIATION[1] = 0; + __LEGACY_MAX_TIME_VARIATION[2] = 0; + __LEGACY_MAX_TIME_VARIATION[3] = 0; + } + function initialize( IBridge bridge_, ISequencerInbox.MaxTimeVariation calldata maxTimeVariation_ ) external onlyDelegated { if (bridge != IBridge(address(0))) revert AlreadyInit(); if (bridge_ == IBridge(address(0))) revert HadZeroInit(); + + // Make sure logic contract was created by proper value for 'isUsingFeeToken'. + // Bridge in ETH based chains doesn't implement nativeToken(). In future it might implement it and return address(0) + bool actualIsUsingFeeToken = false; + try IERC20Bridge(address(bridge_)).nativeToken() returns (address feeToken) { + if (feeToken != address(0)) { + actualIsUsingFeeToken = true; + } + } catch {} + if (isUsingFeeToken != actualIsUsingFeeToken) { + revert NativeTokenMismatch(); + } + bridge = bridge_; rollup = bridge_.rollup(); - maxTimeVariation = maxTimeVariation_; + delayBlocks = maxTimeVariation_.delayBlocks; + futureBlocks = maxTimeVariation_.futureBlocks; + delaySeconds = maxTimeVariation_.delaySeconds; + futureSeconds = maxTimeVariation_.futureSeconds; } /// @notice Allows the rollup owner to sync the rollup address @@ -101,28 +216,74 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox rollup = newRollup; } - function getTimeBounds() internal view virtual returns (TimeBounds memory) { - TimeBounds memory bounds; - if (block.timestamp > maxTimeVariation.delaySeconds) { - bounds.minTimestamp = uint64(block.timestamp - maxTimeVariation.delaySeconds); + function getTimeBounds() internal view virtual returns (IBridge.TimeBounds memory) { + IBridge.TimeBounds memory bounds; + ( + uint64 delayBlocks_, + uint64 futureBlocks_, + uint64 delaySeconds_, + uint64 futureSeconds_ + ) = maxTimeVariationInternal(); + if (block.timestamp > delaySeconds_) { + bounds.minTimestamp = uint64(block.timestamp) - delaySeconds_; } - bounds.maxTimestamp = uint64(block.timestamp + maxTimeVariation.futureSeconds); - if (block.number > maxTimeVariation.delayBlocks) { - bounds.minBlockNumber = uint64(block.number - maxTimeVariation.delayBlocks); + bounds.maxTimestamp = uint64(block.timestamp) + futureSeconds_; + if (block.number > delayBlocks_) { + bounds.minBlockNumber = uint64(block.number) - delayBlocks_; } - bounds.maxBlockNumber = uint64(block.number + maxTimeVariation.futureBlocks); + bounds.maxBlockNumber = uint64(block.number) + futureBlocks_; return bounds; } /// @inheritdoc ISequencerInbox function removeDelayAfterFork() external { if (!_chainIdChanged()) revert NotForked(); - maxTimeVariation = ISequencerInbox.MaxTimeVariation({ - delayBlocks: 1, - futureBlocks: 1, - delaySeconds: 1, - futureSeconds: 1 - }); + delayBlocks = 1; + futureBlocks = 1; + delaySeconds = 1; + futureSeconds = 1; + } + + function maxTimeVariation() + external + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + ( + uint64 delayBlocks_, + uint64 futureBlocks_, + uint64 delaySeconds_, + uint64 futureSeconds_ + ) = maxTimeVariationInternal(); + + return ( + uint256(delayBlocks_), + uint256(futureBlocks_), + uint256(delaySeconds_), + uint256(futureSeconds_) + ); + } + + function maxTimeVariationInternal() + internal + view + returns ( + uint64, + uint64, + uint64, + uint64 + ) + { + if (_chainIdChanged()) { + return (1, 1, 1, 1); + } else { + return (delayBlocks, futureBlocks, delaySeconds, futureSeconds); + } } /// @inheritdoc ISequencerInbox @@ -145,10 +306,8 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox messageDataHash ); // Can only force-include after the Sequencer-only window has expired. - if (l1BlockAndTime[0] + maxTimeVariation.delayBlocks >= block.number) - revert ForceIncludeBlockTooSoon(); - if (l1BlockAndTime[1] + maxTimeVariation.delaySeconds >= block.timestamp) - revert ForceIncludeTimeTooSoon(); + if (l1BlockAndTime[0] + delayBlocks >= block.number) revert ForceIncludeBlockTooSoon(); + if (l1BlockAndTime[1] + delaySeconds >= block.timestamp) revert ForceIncludeTimeTooSoon(); // Verify that message hash represents the last message sequence of delayed message to be included bytes32 prevDelayedAcc = 0; @@ -160,14 +319,12 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox Messages.accumulateInboxMessage(prevDelayedAcc, messageHash) ) revert IncorrectMessagePreimage(); - (bytes32 dataHash, TimeBounds memory timeBounds) = formEmptyDataHash( + (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formEmptyDataHash( _totalDelayedMessagesRead ); uint256 __totalDelayedMessagesRead = _totalDelayedMessagesRead; uint256 prevSeqMsgCount = bridge.sequencerReportedSubMessageCount(); - uint256 newSeqMsgCount = prevSeqMsgCount + - _totalDelayedMessagesRead - - totalDelayedMessagesRead; + uint256 newSeqMsgCount = prevSeqMsgCount; // force inclusion should not modify sequencer message count ( uint256 seqMessageIndex, bytes32 beforeAcc, @@ -187,42 +344,18 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox delayedAcc, totalDelayedMessagesRead, timeBounds, - BatchDataLocation.NoData + IBridge.BatchDataLocation.NoData ); } - /// @dev Deprecated in favor of the variant specifying message counts for consistency + /// @dev Deprecated, kept for abi generation and will be removed in the future function addSequencerL2BatchFromOrigin( - uint256 sequenceNumber, - bytes calldata data, - uint256 afterDelayedMessagesRead, - IGasRefunder gasRefunder - ) external refundsGas(gasRefunder) { - // solhint-disable-next-line avoid-tx-origin - if (msg.sender != tx.origin) revert NotOrigin(); - if (!isBatchPoster[msg.sender]) revert NotBatchPoster(); - - (bytes32 dataHash, TimeBounds memory timeBounds) = formDataHash( - data, - afterDelayedMessagesRead - ); - ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 afterAcc - ) = addSequencerL2BatchImpl(dataHash, afterDelayedMessagesRead, data.length, 0, 0); - if (seqMessageIndex != sequenceNumber) - revert BadSequencerNumber(seqMessageIndex, sequenceNumber); - emit SequencerBatchDelivered( - sequenceNumber, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, - timeBounds, - BatchDataLocation.TxInput - ); + uint256, + bytes calldata, + uint256, + IGasRefunder + ) external pure { + revert Deprecated(); } function addSequencerL2BatchFromOrigin( @@ -232,17 +365,17 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox IGasRefunder gasRefunder, uint256 prevMessageCount, uint256 newMessageCount - ) external refundsGas(gasRefunder) { + ) external refundsGas(gasRefunder, IReader4844(address(0))) { // solhint-disable-next-line avoid-tx-origin if (msg.sender != tx.origin) revert NotOrigin(); if (!isBatchPoster[msg.sender]) revert NotBatchPoster(); - (bytes32 dataHash, TimeBounds memory timeBounds) = formDataHash( + (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash( data, afterDelayedMessagesRead ); // Reformat the stack to prevent "Stack too deep" uint256 sequenceNumber_ = sequenceNumber; - TimeBounds memory timeBounds_ = timeBounds; + IBridge.TimeBounds memory timeBounds_ = timeBounds; bytes32 dataHash_ = dataHash; uint256 dataLength = data.length; uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead; @@ -260,8 +393,12 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox prevMessageCount_, newMessageCount_ ); - if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) + + // ~uint256(0) is type(uint256).max, but ever so slightly cheaper + if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) { revert BadSequencerNumber(seqMessageIndex, sequenceNumber_); + } + emit SequencerBatchDelivered( seqMessageIndex, beforeAcc, @@ -269,10 +406,71 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox delayedAcc, totalDelayedMessagesRead, timeBounds_, - BatchDataLocation.TxInput + IBridge.BatchDataLocation.TxInput ); } + function addSequencerL2BatchFromBlobs( + uint256 sequenceNumber, + uint256 afterDelayedMessagesRead, + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount + ) external refundsGas(gasRefunder, reader4844) { + if (!isBatchPoster[msg.sender]) revert NotBatchPoster(); + ( + bytes32 dataHash, + IBridge.TimeBounds memory timeBounds, + uint256 blobGas + ) = formBlobDataHash(afterDelayedMessagesRead); + + // we use addSequencerL2BatchImpl for submitting the message + // normally this would also submit a batch spending report but that is skipped if we pass + // an empty call data size, then we submit a separate batch spending report later + ( + uint256 seqMessageIndex, + bytes32 beforeAcc, + bytes32 delayedAcc, + bytes32 afterAcc + ) = addSequencerL2BatchImpl( + dataHash, + afterDelayedMessagesRead, + 0, + prevMessageCount, + newMessageCount + ); + + uint256 _sequenceNumber = sequenceNumber; // stack workaround + + // ~uint256(0) is type(uint256).max, but ever so slightly cheaper + if (seqMessageIndex != _sequenceNumber && _sequenceNumber != ~uint256(0)) { + revert BadSequencerNumber(seqMessageIndex, _sequenceNumber); + } + + emit SequencerBatchDelivered( + _sequenceNumber, + beforeAcc, + afterAcc, + delayedAcc, + totalDelayedMessagesRead, + timeBounds, + IBridge.BatchDataLocation.Blob + ); + + // blobs are currently not supported on host arbitrum chains, when support is added it may + // consume gas in a different way to L1, so explicitly block host arb chains so that if support for blobs + // on arb is added it will need to explicitly turned on in the sequencer inbox + if (hostChainIsArbitrum) revert DataBlobsNotSupported(); + + // submit a batch spending report to refund the entity that produced the blob batch data + // same as using calldata, we only submit spending report if the caller is the origin of the tx + // such that one cannot "double-claim" batch posting refund in the same tx + // solhint-disable-next-line avoid-tx-origin + if (msg.sender == tx.origin && !isUsingFeeToken) { + submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, blobGas); + } + } + function addSequencerL2Batch( uint256 sequenceNumber, bytes calldata data, @@ -280,9 +478,9 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox IGasRefunder gasRefunder, uint256 prevMessageCount, uint256 newMessageCount - ) external override refundsGas(gasRefunder) { + ) external override refundsGas(gasRefunder, IReader4844(address(0))) { if (!isBatchPoster[msg.sender] && msg.sender != address(rollup)) revert NotBatchPoster(); - (bytes32 dataHash, TimeBounds memory timeBounds) = formDataHash( + (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash( data, afterDelayedMessagesRead ); @@ -290,7 +488,7 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox { // Reformat the stack to prevent "Stack too deep" uint256 sequenceNumber_ = sequenceNumber; - TimeBounds memory timeBounds_ = timeBounds; + IBridge.TimeBounds memory timeBounds_ = timeBounds; bytes32 dataHash_ = dataHash; uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead; uint256 prevMessageCount_ = prevMessageCount; @@ -307,8 +505,12 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox prevMessageCount_, newMessageCount_ ); - if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) + + // ~uint256(0) is type(uint256).max, but ever so slightly cheaper + if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) { revert BadSequencerNumber(seqMessageIndex, sequenceNumber_); + } + emit SequencerBatchDelivered( seqMessageIndex, beforeAcc, @@ -316,34 +518,18 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox delayedAcc, totalDelayedMessagesRead, timeBounds_, - BatchDataLocation.SeparateBatchEvent + IBridge.BatchDataLocation.SeparateBatchEvent ); } emit SequencerBatchData(seqMessageIndex, data); } - modifier validateBatchData(bytes calldata data) { - uint256 fullDataLen = HEADER_LENGTH + data.length; - if (fullDataLen > maxDataSize) revert DataTooLarge(fullDataLen, maxDataSize); - if (data.length > 0 && (data[0] & DATA_AUTHENTICATED_FLAG) == DATA_AUTHENTICATED_FLAG) { - revert DataNotAuthenticated(); - } - // the first byte is used to identify the type of batch data - // das batches expect to have the type byte set, followed by the keyset (so they should have at least 33 bytes) - if (data.length >= 33 && data[0] & 0x80 != 0) { - // we skip the first byte, then read the next 32 bytes for the keyset - bytes32 dasKeysetHash = bytes32(data[1:33]); - if (!dasKeySetInfo[dasKeysetHash].isValidKeyset) revert NoSuchKeyset(dasKeysetHash); - } - _; - } - function packHeader(uint256 afterDelayedMessagesRead) internal view - returns (bytes memory, TimeBounds memory) + returns (bytes memory, IBridge.TimeBounds memory) { - TimeBounds memory timeBounds = getTimeBounds(); + IBridge.TimeBounds memory timeBounds = getTimeBounds(); bytes memory header = abi.encodePacked( timeBounds.minTimestamp, timeBounds.maxTimestamp, @@ -356,24 +542,139 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox return (header, timeBounds); } - function formDataHash(bytes calldata data, uint256 afterDelayedMessagesRead) + /// @dev Form a hash for a sequencer message with no batch data + /// @param afterDelayedMessagesRead The delayed messages count read up to + /// @return The data hash + /// @return The timebounds within which the message should be processed + function formEmptyDataHash(uint256 afterDelayedMessagesRead) internal view - validateBatchData(data) - returns (bytes32, TimeBounds memory) + returns (bytes32, IBridge.TimeBounds memory) { - (bytes memory header, TimeBounds memory timeBounds) = packHeader(afterDelayedMessagesRead); - bytes32 dataHash = keccak256(bytes.concat(header, data)); - return (dataHash, timeBounds); + (bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader( + afterDelayedMessagesRead + ); + return (keccak256(header), timeBounds); } - function formEmptyDataHash(uint256 afterDelayedMessagesRead) + /// @dev Since the data is supplied from calldata, the batch poster can choose the data type + /// We need to ensure that this data cannot cause a collision with data supplied via another method (eg blobs) + /// therefore we restrict which flags can be provided as a header in this field + /// This also safe guards unused flags for future use, as we know they would have been disallowed up until this point + /// @param headerByte The first byte in the calldata + function isValidCallDataFlag(bytes1 headerByte) internal pure returns (bool) { + return + headerByte == BROTLI_MESSAGE_HEADER_FLAG || + headerByte == DAS_MESSAGE_HEADER_FLAG || + (headerByte == (DAS_MESSAGE_HEADER_FLAG | TREE_DAS_MESSAGE_HEADER_FLAG)) || + headerByte == ZERO_HEAVY_MESSAGE_HEADER_FLAG; + } + + /// @dev Form a hash of the data taken from the calldata + /// @param data The calldata to be hashed + /// @param afterDelayedMessagesRead The delayed messages count read up to + /// @return The data hash + /// @return The timebounds within which the message should be processed + function formCallDataHash(bytes calldata data, uint256 afterDelayedMessagesRead) internal view - returns (bytes32, TimeBounds memory) + returns (bytes32, IBridge.TimeBounds memory) { - (bytes memory header, TimeBounds memory timeBounds) = packHeader(afterDelayedMessagesRead); - return (keccak256(header), timeBounds); + uint256 fullDataLen = HEADER_LENGTH + data.length; + if (fullDataLen > maxDataSize) revert DataTooLarge(fullDataLen, maxDataSize); + + (bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader( + afterDelayedMessagesRead + ); + + // the batch poster is allowed to submit an empty batch, they can use this to progress the + // delayed inbox without providing extra batch data + if (data.length > 0) { + // The first data byte cannot be the same as any that have been set via other methods (eg 4844 blob header) as this + // would allow the supplier of the data to spoof an incorrect 4844 data batch + if (!isValidCallDataFlag(data[0])) revert InvalidHeaderFlag(data[0]); + + // the first byte is used to identify the type of batch data + // das batches expect to have the type byte set, followed by the keyset (so they should have at least 33 bytes) + // if invalid data is supplied here the state transition function will process it as an empty block + // however we can provide a nice additional check here for the batch poster + if (data[0] & DAS_MESSAGE_HEADER_FLAG != 0 && data.length >= 33) { + // we skip the first byte, then read the next 32 bytes for the keyset + bytes32 dasKeysetHash = bytes32(data[1:33]); + if (!dasKeySetInfo[dasKeysetHash].isValidKeyset) revert NoSuchKeyset(dasKeysetHash); + } + } + return (keccak256(bytes.concat(header, data)), timeBounds); + } + + /// @dev Form a hash of the data being provided in 4844 data blobs + /// @param afterDelayedMessagesRead The delayed messages count read up to + /// @return The data hash + /// @return The timebounds within which the message should be processed + /// @return The normalized amount of gas used for blob posting + function formBlobDataHash(uint256 afterDelayedMessagesRead) + internal + view + returns ( + bytes32, + IBridge.TimeBounds memory, + uint256 + ) + { + bytes32[] memory dataHashes = reader4844.getDataHashes(); + if (dataHashes.length == 0) revert MissingDataHashes(); + + (bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader( + afterDelayedMessagesRead + ); + + uint256 blobCost = reader4844.getBlobBaseFee() * GAS_PER_BLOB * dataHashes.length; + return ( + keccak256(bytes.concat(header, DATA_BLOB_HEADER_FLAG, abi.encodePacked(dataHashes))), + timeBounds, + block.basefee > 0 ? blobCost / block.basefee : 0 + ); + } + + /// @dev Submit a batch spending report message so that the batch poster can be reimbursed on the rollup + /// This function expect msg.sender is tx.origin, and will always record tx.origin as the spender + /// @param dataHash The hash of the message the spending report is being submitted for + /// @param seqMessageIndex The index of the message to submit the spending report for + /// @param gasPrice The gas price that was paid for the data (standard gas or data gas) + function submitBatchSpendingReport( + bytes32 dataHash, + uint256 seqMessageIndex, + uint256 gasPrice, + uint256 extraGas + ) internal { + // report the account who paid the gas (tx.origin) for the tx as batch poster + // if msg.sender is used and is a contract, it might not be able to spend the refund on l2 + // solhint-disable-next-line avoid-tx-origin + address batchPoster = tx.origin; + + // this msg isn't included in the current sequencer batch, but instead added to + // the delayed messages queue that is yet to be included + if (hostChainIsArbitrum) { + // Include extra gas for the host chain's L1 gas charging + uint256 l1Fees = ArbGasInfo(address(0x6c)).getCurrentTxL1GasFees(); + extraGas += l1Fees / block.basefee; + } + require(extraGas <= type(uint64).max, "EXTRA_GAS_NOT_UINT64"); + bytes memory spendingReportMsg = abi.encodePacked( + block.timestamp, + batchPoster, + dataHash, + seqMessageIndex, + gasPrice, + uint64(extraGas) + ); + + uint256 msgNum = bridge.submitBatchSpendingReport( + batchPoster, + keccak256(spendingReportMsg) + ); + // this is the same event used by Inbox.sol after including a message to the delayed message accumulator + emit InboxMessageDelivered(msgNum, spendingReportMsg); } function addSequencerL2BatchImpl( @@ -403,39 +704,9 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox totalDelayedMessagesRead = afterDelayedMessagesRead; - if (calldataLengthPosted > 0) { - // this msg isn't included in the current sequencer batch, but instead added to - // the delayed messages queue that is yet to be included - address batchPoster = msg.sender; - bytes memory spendingReportMsg; - if (hostChainIsArbitrum) { - // Include extra gas for the host chain's L1 gas charging - uint256 l1Fees = ArbGasInfo(address(0x6c)).getCurrentTxL1GasFees(); - uint256 extraGas = l1Fees / block.basefee; - require(extraGas <= type(uint64).max, "L1_GAS_NOT_UINT64"); - spendingReportMsg = abi.encodePacked( - block.timestamp, - batchPoster, - dataHash, - seqMessageIndex, - block.basefee, - uint64(extraGas) - ); - } else { - spendingReportMsg = abi.encodePacked( - block.timestamp, - batchPoster, - dataHash, - seqMessageIndex, - block.basefee - ); - } - uint256 msgNum = bridge.submitBatchSpendingReport( - batchPoster, - keccak256(spendingReportMsg) - ); - // this is the same event used by Inbox.sol after including a message to the delayed message accumulator - emit InboxMessageDelivered(msgNum, spendingReportMsg); + if (calldataLengthPosted > 0 && !isUsingFeeToken) { + // only report batch poster spendings if chain is using ETH as native currency + submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, 0); } } @@ -452,12 +723,18 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox external onlyRollupOwner { - maxTimeVariation = maxTimeVariation_; + delayBlocks = maxTimeVariation_.delayBlocks; + futureBlocks = maxTimeVariation_.futureBlocks; + delaySeconds = maxTimeVariation_.delaySeconds; + futureSeconds = maxTimeVariation_.futureSeconds; emit OwnerFunctionCalled(0); } /// @inheritdoc ISequencerInbox - function setIsBatchPoster(address addr, bool isBatchPoster_) external onlyRollupOwner { + function setIsBatchPoster(address addr, bool isBatchPoster_) + external + onlyRollupOwnerOrBatchPosterManager + { isBatchPoster[addr] = isBatchPoster_; emit OwnerFunctionCalled(1); } @@ -493,9 +770,18 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox } /// @inheritdoc ISequencerInbox - function setIsSequencer(address addr, bool isSequencer_) external onlyRollupOwner { + function setIsSequencer(address addr, bool isSequencer_) + external + onlyRollupOwnerOrBatchPosterManager + { isSequencer[addr] = isSequencer_; - emit OwnerFunctionCalled(4); + emit OwnerFunctionCalled(4); // Owner in this context can also be batch poster manager + } + + /// @inheritdoc ISequencerInbox + function setBatchPosterManager(address newBatchPosterManager) external onlyRollupOwner { + batchPosterManager = newBatchPosterManager; + emit OwnerFunctionCalled(5); } function isValidKeysetHash(bytes32 ksHash) external view returns (bool) { diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index 12cad0852..c5427e7ea 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -110,6 +110,12 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { osp = osp_; } + function postUpgradeInit(IOneStepProofEntry osp_) external onlyDelegated onlyProxyOwner { + // when updating to 4844 we need to create new osp contracts and set them here + // on the challenge manager + osp = osp_; + } + function createChallenge( bytes32 wasmModuleRoot_, MachineStatus[2] calldata startAndEndMachineStatuses_, diff --git a/src/libraries/Error.sol b/src/libraries/Error.sol index fd6f3255f..7c2860fe8 100644 --- a/src/libraries/Error.sol +++ b/src/libraries/Error.sol @@ -176,5 +176,32 @@ error AlreadyValidDASKeyset(bytes32); /// @dev Tried to use or invalidate an already invalid Data Availability Service keyset error NoSuchKeyset(bytes32); +/// @dev Thrown when the provided address is not the designated batch poster manager +error NotBatchPosterManager(address); + +/// @dev Thrown when a data blob feature is attempted to be used on a chain that doesnt support it +error DataBlobsNotSupported(); + +/// @dev Thrown when an init param was supplied as empty +error InitParamZero(string name); + +/// @dev Thrown when data hashes where expected but not where present on the tx +error MissingDataHashes(); + +/// @dev Thrown when the data blob meta data is invalid +error InvalidBlobMetadata(); + /// @dev Thrown when rollup is not updated with updateRollupAddress error RollupNotChanged(); + +/// @dev Batch data was empty when non empty was expected +error EmptyBatchData(); + +/// @dev Unsupported header flag was provided +error InvalidHeaderFlag(bytes1); + +/// @dev SequencerInbox and Bridge are not in the same feeToken/ETH mode +error NativeTokenMismatch(); + +/// @dev Thrown when a deprecated function is called +error Deprecated(); diff --git a/src/libraries/GasRefundEnabled.sol b/src/libraries/GasRefundEnabled.sol new file mode 100644 index 000000000..63a5bbfe9 --- /dev/null +++ b/src/libraries/GasRefundEnabled.sol @@ -0,0 +1,52 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +// solhint-disable-next-line compiler-version +pragma solidity ^0.8.0; + +import "./IReader4844.sol"; +import "./IGasRefunder.sol"; + +abstract contract GasRefundEnabled { + uint256 internal immutable gasPerBlob = 2**17; + + /// @dev this refunds the sender for execution costs of the tx + /// calldata costs are only refunded if `msg.sender == tx.origin` to guarantee the value refunded relates to charging + /// for the `tx.input`. this avoids a possible attack where you generate large calldata from a contract and get over-refunded + modifier refundsGas(IGasRefunder gasRefunder, IReader4844 reader4844) { + uint256 startGasLeft = gasleft(); + _; + if (address(gasRefunder) != address(0)) { + uint256 calldataSize = msg.data.length; + uint256 calldataWords = (calldataSize + 31) / 32; + // account for the CALLDATACOPY cost of the proxy contract, including the memory expansion cost + startGasLeft += calldataWords * 6 + (calldataWords**2) / 512; + // if triggered in a contract call, the spender may be overrefunded by appending dummy data to the call + // so we check if it is a top level call, which would mean the sender paid calldata as part of tx.input + // solhint-disable-next-line avoid-tx-origin + if (msg.sender != tx.origin) { + // We can't be sure if this calldata came from the top level tx, + // so to be safe we tell the gas refunder there was no calldata. + calldataSize = 0; + } else { + // for similar reasons to above we only refund blob gas when the tx.origin is the msg.sender + // this avoids the caller being able to send blobs to other contracts and still get refunded here + if (address(reader4844) != address(0)) { + // add any cost for 4844 data, the data hash reader throws an error prior to 4844 being activated + // we do this addition here rather in the GasRefunder so that we can check the msg.sender is the tx.origin + try reader4844.getDataHashes() returns (bytes32[] memory dataHashes) { + if (dataHashes.length != 0) { + uint256 blobBasefee = reader4844.getBlobBaseFee(); + startGasLeft += + (dataHashes.length * gasPerBlob * blobBasefee) / + block.basefee; + } + } catch {} + } + } + + gasRefunder.onGasSpent(payable(msg.sender), startGasLeft - gasleft(), calldataSize); + } + } +} diff --git a/src/libraries/IGasRefunder.sol b/src/libraries/IGasRefunder.sol index e7b086565..f80ac3b2b 100644 --- a/src/libraries/IGasRefunder.sol +++ b/src/libraries/IGasRefunder.sol @@ -12,28 +12,3 @@ interface IGasRefunder { uint256 calldataSize ) external returns (bool success); } - -abstract contract GasRefundEnabled { - /// @dev this refunds the sender for execution costs of the tx - /// calldata costs are only refunded if `msg.sender == tx.origin` to guarantee the value refunded relates to charging - /// for the `tx.input`. this avoids a possible attack where you generate large calldata from a contract and get over-refunded - modifier refundsGas(IGasRefunder gasRefunder) { - uint256 startGasLeft = gasleft(); - _; - if (address(gasRefunder) != address(0)) { - uint256 calldataSize = msg.data.length; - uint256 calldataWords = (calldataSize + 31) / 32; - // account for the CALLDATACOPY cost of the proxy contract, including the memory expansion cost - startGasLeft += calldataWords * 6 + (calldataWords**2) / 512; - // if triggered in a contract call, the spender may be overrefunded by appending dummy data to the call - // so we check if it is a top level call, which would mean the sender paid calldata as part of tx.input - // solhint-disable-next-line avoid-tx-origin - if (msg.sender != tx.origin) { - // We can't be sure if this calldata came from the top level tx, - // so to be safe we tell the gas refunder there was no calldata. - calldataSize = 0; - } - gasRefunder.onGasSpent(payable(msg.sender), startGasLeft - gasleft(), calldataSize); - } - } -} diff --git a/src/libraries/IReader4844.sol b/src/libraries/IReader4844.sol new file mode 100644 index 000000000..5d3ad2ad8 --- /dev/null +++ b/src/libraries/IReader4844.sol @@ -0,0 +1,13 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity >=0.6.9 <0.9.0; + +interface IReader4844 { + /// @notice Returns the current BLOBBASEFEE + function getBlobBaseFee() external view returns (uint256); + + /// @notice Returns all the data hashes of all the blobs on the current transaction + function getDataHashes() external view returns (bytes32[] memory); +} diff --git a/src/mocks/InboxStub.sol b/src/mocks/InboxStub.sol index 3f097a357..182dda678 100644 --- a/src/mocks/InboxStub.sol +++ b/src/mocks/InboxStub.sol @@ -163,7 +163,7 @@ contract InboxStub is IInboxBase, IInbox { address, uint256, bytes calldata - ) external returns (uint256) { + ) external pure returns (uint256) { revert("NOT_IMPLEMENTED"); } @@ -173,7 +173,7 @@ contract InboxStub is IInboxBase, IInbox { uint256, uint256, address - ) external returns (uint256) { + ) external pure returns (uint256) { revert("NOT_IMPLEMENTED"); } diff --git a/src/mocks/SequencerInboxStub.sol b/src/mocks/SequencerInboxStub.sol index ade7b6003..e78d94795 100644 --- a/src/mocks/SequencerInboxStub.sol +++ b/src/mocks/SequencerInboxStub.sol @@ -13,11 +13,16 @@ contract SequencerInboxStub is SequencerInbox { IBridge bridge_, address sequencer_, ISequencerInbox.MaxTimeVariation memory maxTimeVariation_, - uint256 maxDataSize_ - ) SequencerInbox(maxDataSize_) { + uint256 maxDataSize_, + IReader4844 reader4844_, + bool isUsingFeeToken_ + ) SequencerInbox(maxDataSize_, reader4844_, isUsingFeeToken_) { bridge = bridge_; rollup = IOwnable(msg.sender); - maxTimeVariation = maxTimeVariation_; + delayBlocks = maxTimeVariation_.delayBlocks; + futureBlocks = maxTimeVariation_.futureBlocks; + delaySeconds = maxTimeVariation_.delaySeconds; + futureSeconds = maxTimeVariation_.futureSeconds; isBatchPoster[sequencer_] = true; } @@ -30,7 +35,7 @@ contract SequencerInboxStub is SequencerInbox { ); require(num == 0, "ALREADY_DELAYED_INIT"); emit InboxMessageDelivered(num, initMsg); - (bytes32 dataHash, TimeBounds memory timeBounds) = formEmptyDataHash(1); + (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formEmptyDataHash(1); ( uint256 sequencerMessageCount, bytes32 beforeAcc, @@ -45,11 +50,11 @@ contract SequencerInboxStub is SequencerInbox { delayedAcc, totalDelayedMessagesRead, timeBounds, - BatchDataLocation.NoData + IBridge.BatchDataLocation.NoData ); } - function getTimeBounds() internal view override returns (TimeBounds memory bounds) { + function getTimeBounds() internal view override returns (IBridge.TimeBounds memory bounds) { this; // silence warning about function not being view return bounds; } diff --git a/src/mocks/Simple.sol b/src/mocks/Simple.sol index f7e3f7f47..b9aafc74c 100644 --- a/src/mocks/Simple.sol +++ b/src/mocks/Simple.sol @@ -4,11 +4,13 @@ pragma solidity ^0.8.0; +import "../bridge/ISequencerInbox.sol"; import "../precompiles/ArbRetryableTx.sol"; import "../precompiles/ArbSys.sol"; contract Simple { uint64 public counter; + uint256 public difficulty; event CounterEvent(uint64 count); event RedeemedEvent(address caller, address redeemer); @@ -30,6 +32,7 @@ contract Simple { } function incrementRedeem() external { + // solhint-disable-next-line avoid-tx-origin require(msg.sender == tx.origin, "SENDER_NOT_ORIGIN"); require(ArbSys(address(0x64)).wasMyCallersAddressAliased(), "NOT_ALIASED"); counter++; @@ -45,8 +48,12 @@ contract Simple { return block.number; } + function storeDifficulty() external { + difficulty = block.difficulty; + } + function getBlockDifficulty() external view returns (uint256) { - return block.difficulty; + return difficulty; } function noop() external pure {} @@ -93,6 +100,7 @@ contract Simple { useTopLevel, delegateCase ); + // solhint-disable-next-line avoid-low-level-calls (bool success, ) = address(this).delegatecall(data); require(success, "DELEGATE_CALL_FAILED"); @@ -113,6 +121,7 @@ contract Simple { useTopLevel, callCase ); + // solhint-disable-next-line avoid-low-level-calls (success, ) = address(this).call(data); require(success, "CALL_FAILED"); } @@ -121,8 +130,29 @@ contract Simple { uint256 before = gasleft(); // The inner call may revert, but we still want to return the amount of gas used, // so we ignore the result of this call. + // solhint-disable-next-line avoid-low-level-calls // solc-ignore-next-line unused-call-retval to.staticcall{gas: before - 10000}(input); return before - gasleft(); } + + function postManyBatches( + ISequencerInbox sequencerInbox, + bytes memory batchData, + uint256 numberToPost + ) external { + uint256 sequenceNumber = sequencerInbox.batchCount(); + uint256 delayedMessagesRead = sequencerInbox.totalDelayedMessagesRead(); + for (uint256 i = 0; i < numberToPost; i++) { + sequencerInbox.addSequencerL2Batch( + sequenceNumber, + batchData, + delayedMessagesRead, + IGasRefunder(address(0)), + 0, + 0 + ); + sequenceNumber++; + } + } } diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index 260ab2062..ec0ed85ed 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -101,16 +101,35 @@ contract OneStepProverHostIo is IOneStepProver { state.u64Vals[idx] = val; } + uint256 internal constant BLS_MODULUS = + 52435875175126190479447740508185965837690552500527637822603658699938581184513; + uint256 internal constant PRIMITIVE_ROOT_OF_UNITY = + 10238227357739495823651030575849232062558860180284477541189508159991286009131; + + // Computes b**e % m + // Really pure but the Solidity compiler sees the staticcall and requires view + function modExp256( + uint256 b, + uint256 e, + uint256 m + ) internal view returns (uint256) { + bytes memory modExpInput = abi.encode(32, 32, 32, b, e, m); + (bool modexpSuccess, bytes memory modExpOutput) = address(0x05).staticcall(modExpInput); + require(modexpSuccess, "MODEXP_FAILED"); + require(modExpOutput.length == 32, "MODEXP_WRONG_LENGTH"); + return uint256(bytes32(modExpOutput)); + } + function executeReadPreImage( ExecutionContext calldata, Machine memory mach, Module memory mod, - Instruction calldata, + Instruction calldata inst, bytes calldata proof - ) internal pure { + ) internal view { uint256 preimageOffset = mach.valueStack.pop().assumeI32(); uint256 ptr = mach.valueStack.pop().assumeI32(); - if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) { + if (preimageOffset % 32 != 0 || ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) { mach.status = MachineStatus.ERRORED; return; } @@ -128,18 +147,92 @@ contract OneStepProverHostIo is IOneStepProver { bytes memory extracted; uint8 proofType = uint8(proof[proofOffset]); proofOffset++; - if (proofType == 0) { + // These values must be kept in sync with `arbitrator/arbutil/src/types.rs` + // and `arbutil/preimage_type.go` (both in the nitro repo). + if (inst.argumentData == 0) { + // The machine is asking for a keccak256 preimage + + if (proofType == 0) { + bytes calldata preimage = proof[proofOffset:]; + require(keccak256(preimage) == leafContents, "BAD_PREIMAGE"); + + uint256 preimageEnd = preimageOffset + 32; + if (preimageEnd > preimage.length) { + preimageEnd = preimage.length; + } + extracted = preimage[preimageOffset:preimageEnd]; + } else { + // TODO: support proving via an authenticated contract + revert("UNKNOWN_PREIMAGE_PROOF"); + } + } else if (inst.argumentData == 1) { + // The machine is asking for a sha2-256 preimage + + require(proofType == 0, "UNKNOWN_PREIMAGE_PROOF"); bytes calldata preimage = proof[proofOffset:]; - require(keccak256(preimage) == leafContents, "BAD_PREIMAGE"); + require(sha256(preimage) == leafContents, "BAD_PREIMAGE"); uint256 preimageEnd = preimageOffset + 32; if (preimageEnd > preimage.length) { preimageEnd = preimage.length; } extracted = preimage[preimageOffset:preimageEnd]; + } else if (inst.argumentData == 2) { + // The machine is asking for an Ethereum versioned hash preimage + + require(proofType == 0, "UNKNOWN_PREIMAGE_PROOF"); + + // kzgProof should be a valid input to the EIP-4844 point evaluation precompile at address 0x0A. + // It should prove the preimageOffset/32'th word of the machine's requested KZG commitment. + bytes calldata kzgProof = proof[proofOffset:]; + + require(bytes32(kzgProof[:32]) == leafContents, "KZG_PROOF_WRONG_HASH"); + + uint256 fieldElementsPerBlob; + uint256 blsModulus; + { + (bool success, bytes memory kzgParams) = address(0x0A).staticcall(kzgProof); + require(success, "INVALID_KZG_PROOF"); + require(kzgParams.length > 0, "KZG_PRECOMPILE_MISSING"); + (fieldElementsPerBlob, blsModulus) = abi.decode(kzgParams, (uint256, uint256)); + } + + // With a hardcoded PRIMITIVE_ROOT_OF_UNITY, we can only support this BLS modulus. + // It may be worth in the future supporting arbitrary BLS moduli, but we would likely need to + // validate a user-supplied root of unity. + require(blsModulus == BLS_MODULUS, "UNKNOWN_BLS_MODULUS"); + + // If preimageOffset is greater than or equal to the blob size, leave extracted empty and call it here. + if (preimageOffset < fieldElementsPerBlob * 32) { + // We need to compute what point the polynomial should be evaluated at to get the right part of the preimage. + // KZG commitments use a bit reversal permutation to order the roots of unity. + // To account for that, we reverse the bit order of the index. + uint256 bitReversedIndex = 0; + // preimageOffset was required to be 32 byte aligned above + uint256 tmp = preimageOffset / 32; + for (uint256 i = 1; i < fieldElementsPerBlob; i <<= 1) { + bitReversedIndex <<= 1; + if (tmp & 1 == 1) { + bitReversedIndex |= 1; + } + tmp >>= 1; + } + + // First, we get the root of unity of order 2**fieldElementsPerBlob. + // We start with a root of unity of order 2**32 and then raise it to + // the power of (2**32)/fieldElementsPerBlob to get root of unity we need. + uint256 rootOfUnityPower = (1 << 32) / fieldElementsPerBlob; + // Then, we raise the root of unity to the power of bitReversedIndex, + // to retrieve this word of the KZG commitment. + rootOfUnityPower *= bitReversedIndex; + // z is the point the polynomial is evaluated at to retrieve this word of data + uint256 z = modExp256(PRIMITIVE_ROOT_OF_UNITY, rootOfUnityPower, blsModulus); + require(bytes32(kzgProof[32:64]) == bytes32(z), "KZG_PROOF_WRONG_Z"); + + extracted = kzgProof[64:96]; + } } else { - // TODO: support proving via an authenticated contract - revert("UNKNOWN_PREIMAGE_PROOF"); + revert("UNKNOWN_PREIMAGE_TYPE"); } for (uint256 i = 0; i < extracted.length; i++) { diff --git a/src/precompiles/ArbGasInfo.sol b/src/precompiles/ArbGasInfo.sol index 31dd70eab..b4c210960 100644 --- a/src/precompiles/ArbGasInfo.sol +++ b/src/precompiles/ArbGasInfo.sol @@ -129,4 +129,24 @@ interface ArbGasInfo { /// @notice Returns the available funds from L1 fees function getL1FeesAvailable() external view returns (uint256); + + /// @notice Returns the equilibration units parameter for L1 price adjustment algorithm + /// Available in ArbOS version 20 + function getL1PricingEquilibrationUnits() external view returns (uint256); + + /// @notice Returns the last time the L1 calldata pricer was updated. + /// Available in ArbOS version 20 + function getLastL1PricingUpdateTime() external view returns (uint64); + + /// @notice Returns the amount of L1 calldata payments due for rewards (per the L1 reward rate) + /// Available in ArbOS version 20 + function getL1PricingFundsDueForRewards() external view returns (uint256); + + /// @notice Returns the amount of L1 calldata posted since the last update. + /// Available in ArbOS version 20 + function getL1PricingUnitsSinceUpdate() external view returns (uint64); + + /// @notice Returns the L1 pricing surplus as of the last update (may be negative). + /// Available in ArbOS version 20 + function getLastL1PricingSurplus() external view returns (int256); } diff --git a/src/precompiles/ArbOwnerPublic.sol b/src/precompiles/ArbOwnerPublic.sol index ee9e23470..0de57ce62 100644 --- a/src/precompiles/ArbOwnerPublic.sol +++ b/src/precompiles/ArbOwnerPublic.sol @@ -29,5 +29,13 @@ interface ArbOwnerPublic { /// @notice Get the Brotli compression level used for fast compression function getBrotliCompressionLevel() external view returns (uint64); + /// @notice Get the next scheduled ArbOS version upgrade and its activation timestamp. + /// Returns (0, 0) if no ArbOS upgrade is scheduled. + /// Available in ArbOS version 20. + function getScheduledUpgrade() + external + view + returns (uint64 arbosVersion, uint64 scheduledForTimestamp); + event ChainOwnerRectified(address rectifiedOwner); } diff --git a/src/rollup/BridgeCreator.sol b/src/rollup/BridgeCreator.sol index 01df1c047..0e45f8152 100644 --- a/src/rollup/BridgeCreator.sol +++ b/src/rollup/BridgeCreator.sol @@ -96,7 +96,7 @@ contract BridgeCreator is Ownable { } else { IERC20Bridge(address(frame.bridge)).initialize(IOwnable(rollup), nativeToken); } - frame.sequencerInbox.initialize(frame.bridge, maxTimeVariation); + frame.sequencerInbox.initialize(IBridge(frame.bridge), maxTimeVariation); frame.inbox.initialize(frame.bridge, frame.sequencerInbox); frame.rollupEventInbox.initialize(frame.bridge); frame.outbox.initialize(frame.bridge); diff --git a/src/rollup/RollupCreator.sol b/src/rollup/RollupCreator.sol index ac9621b38..0a7d39b3a 100644 --- a/src/rollup/RollupCreator.sol +++ b/src/rollup/RollupCreator.sol @@ -35,12 +35,14 @@ contract RollupCreator is Ownable { struct RollupDeploymentParams { Config config; - address batchPoster; address[] validators; uint256 maxDataSize; address nativeToken; bool deployFactoriesToL2; uint256 maxFeePerGasForRetryables; + //// @dev The address of the batch poster, not used when set to zero address + address[] batchPosters; + address batchPosterManager; } BridgeCreator public bridgeCreator; @@ -102,6 +104,7 @@ contract RollupCreator is Ownable { * anyone can try to deploy factories and potentially burn the nonce 0 (ie. due to gas price spike when doing direct * L2 TX). That would mean we permanently lost capability to deploy deterministic factory at expected address. * - maxFeePerGasForRetryables price bid for L2 execution. + * - dataHashReader The address of the data hash reader used to read blob hashes * @return The address of the newly created rollup */ function createRollup(RollupDeploymentParams memory deployParams) @@ -109,22 +112,27 @@ contract RollupCreator is Ownable { payable returns (address) { - // Make sure the immutable maxDataSize is as expected - (, ISequencerInbox ethSequencerInbox, IInboxBase ethInbox, , ) = bridgeCreator - .ethBasedTemplates(); - require( - deployParams.maxDataSize == ethSequencerInbox.maxDataSize(), - "SI_MAX_DATA_SIZE_MISMATCH" - ); - require(deployParams.maxDataSize == ethInbox.maxDataSize(), "I_MAX_DATA_SIZE_MISMATCH"); + { + // Make sure the immutable maxDataSize is as expected + (, ISequencerInbox ethSequencerInbox, IInboxBase ethInbox, , ) = bridgeCreator + .ethBasedTemplates(); + require( + deployParams.maxDataSize == ethSequencerInbox.maxDataSize(), + "SI_MAX_DATA_SIZE_MISMATCH" + ); + require(deployParams.maxDataSize == ethInbox.maxDataSize(), "I_MAX_DATA_SIZE_MISMATCH"); - (, ISequencerInbox erc20SequencerInbox, IInboxBase erc20Inbox, , ) = bridgeCreator - .erc20BasedTemplates(); - require( - deployParams.maxDataSize == erc20SequencerInbox.maxDataSize(), - "SI_MAX_DATA_SIZE_MISMATCH" - ); - require(deployParams.maxDataSize == erc20Inbox.maxDataSize(), "I_MAX_DATA_SIZE_MISMATCH"); + (, ISequencerInbox erc20SequencerInbox, IInboxBase erc20Inbox, , ) = bridgeCreator + .erc20BasedTemplates(); + require( + deployParams.maxDataSize == erc20SequencerInbox.maxDataSize(), + "SI_MAX_DATA_SIZE_MISMATCH" + ); + require( + deployParams.maxDataSize == erc20Inbox.maxDataSize(), + "I_MAX_DATA_SIZE_MISMATCH" + ); + } // create proxy admin which will manage bridge contracts ProxyAdmin proxyAdmin = new ProxyAdmin(); @@ -180,9 +188,12 @@ contract RollupCreator is Ownable { }) ); - // setting batch poster, if the address provided is not zero address - if (deployParams.batchPoster != address(0)) { - bridgeContracts.sequencerInbox.setIsBatchPoster(deployParams.batchPoster, true); + // Setting batch posters and batch poster manager + for (uint256 i = 0; i < deployParams.batchPosters.length; i++) { + bridgeContracts.sequencerInbox.setIsBatchPoster(deployParams.batchPosters[i], true); + } + if (deployParams.batchPosterManager != address(0)) { + bridgeContracts.sequencerInbox.setBatchPosterManager(deployParams.batchPosterManager); } // Call setValidator on the newly created rollup contract just if validator set is not empty @@ -257,6 +268,7 @@ contract RollupCreator is Ownable { l2FactoriesDeployer.perform{value: cost}(_inbox, _nativeToken, _maxFeePerGas); // refund the caller + // solhint-disable-next-line avoid-low-level-calls (bool sent, ) = msg.sender.call{value: address(this).balance}(""); require(sent, "Refund failed"); } else { diff --git a/src/rollup/ValidatorWallet.sol b/src/rollup/ValidatorWallet.sol index 9b8c2296c..0d7cbaceb 100644 --- a/src/rollup/ValidatorWallet.sol +++ b/src/rollup/ValidatorWallet.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "../challenge/IChallengeManager.sol"; import "../libraries/DelegateCallAware.sol"; import "../libraries/IGasRefunder.sol"; +import "../libraries/GasRefundEnabled.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -109,7 +110,7 @@ contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnab bytes[] calldata data, address[] calldata destination, uint256[] calldata amount - ) public payable onlyExecutorOrOwner refundsGas(gasRefunder) { + ) public payable onlyExecutorOrOwner refundsGas(gasRefunder, IReader4844(address(0))) { uint256 numTxes = data.length; if (numTxes != destination.length) revert BadArrayLength(numTxes, destination.length); if (numTxes != amount.length) revert BadArrayLength(numTxes, amount.length); @@ -144,7 +145,7 @@ contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnab bytes calldata data, address destination, uint256 amount - ) public payable onlyExecutorOrOwner refundsGas(gasRefunder) { + ) public payable onlyExecutorOrOwner refundsGas(gasRefunder, IReader4844(address(0))) { if (data.length > 0) require(destination.isContract(), "NO_CODE_AT_ADDR"); validateExecuteTransaction(destination); // We use a low level call here to allow for contract and non-contract calls @@ -168,7 +169,7 @@ contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnab IGasRefunder gasRefunder, IChallengeManager manager, uint64[] calldata challenges - ) public onlyExecutorOrOwner refundsGas(gasRefunder) { + ) public onlyExecutorOrOwner refundsGas(gasRefunder, IReader4844(address(0))) { uint256 challengesCount = challenges.length; for (uint256 i = 0; i < challengesCount; i++) { try manager.timeout(challenges[i]) {} catch (bytes memory error) { diff --git a/src/test-helpers/RollupMock.sol b/src/test-helpers/RollupMock.sol index 085b9c005..9abcc6db0 100644 --- a/src/test-helpers/RollupMock.sol +++ b/src/test-helpers/RollupMock.sol @@ -8,6 +8,12 @@ contract RollupMock { event WithdrawTriggered(); event ZombieTriggered(); + address public owner; + + constructor(address _owner) { + owner = _owner; + } + function withdrawStakerFunds() external returns (uint256) { emit WithdrawTriggered(); return 0; diff --git a/test/contract/arbRollup.spec.ts b/test/contract/arbRollup.spec.ts index ac836d1d9..3571c9489 100644 --- a/test/contract/arbRollup.spec.ts +++ b/test/contract/arbRollup.spec.ts @@ -15,7 +15,7 @@ */ /* eslint-env node, mocha */ -import { ethers, network, run } from 'hardhat' +import { ethers, network } from 'hardhat' import { Signer } from '@ethersproject/abstract-signer' import { BigNumberish, BigNumber } from '@ethersproject/bignumber' import { BytesLike } from '@ethersproject/bytes' @@ -82,9 +82,11 @@ const ZERO_ADDR = ethers.constants.AddressZero const extraChallengeTimeBlocks = 20 const wasmModuleRoot = '0x9900000000000000000000000000000000000000000000000000000000000010' +const dummy4844Reader = '0x0000000000000000000000000000000000000089' // let rollup: RollupContract let rollup: RollupContract +let batchPosterManager: Signer let rollupUser: RollupUserLogic let rollupAdmin: RollupAdminLogic let bridge: Bridge @@ -95,7 +97,7 @@ let admin: Signer let sequencer: Signer let challengeManager: ChallengeManager let upgradeExecutor: string -let adminproxy: string +// let adminproxy: string async function getDefaultConfig( _confirmPeriodBlocks = confirmationPeriodBlocks @@ -121,7 +123,7 @@ async function getDefaultConfig( } const setup = async () => { - const accounts = await initializeAccounts() + accounts = await initializeAccounts() admin = accounts[0] const user = accounts[1] @@ -131,6 +133,7 @@ const setup = async () => { const val3 = accounts[4] const val4 = accounts[5] sequencer = accounts[6] + const batchPosterManager = accounts[7] const oneStep0Fac = (await ethers.getContractFactory( 'OneStepProver0' @@ -188,7 +191,11 @@ const setup = async () => { const ethSequencerInboxFac = (await ethers.getContractFactory( 'SequencerInbox' )) as SequencerInbox__factory - const ethSequencerInbox = await ethSequencerInboxFac.deploy(117964) + const ethSequencerInbox = await ethSequencerInboxFac.deploy( + 117964, + dummy4844Reader, + false + ) const ethInboxFac = (await ethers.getContractFactory( 'Inbox' @@ -210,7 +217,14 @@ const setup = async () => { )) as ERC20Bridge__factory const erc20Bridge = await erc20BridgeFac.deploy() - const erc20SequencerInbox = ethSequencerInbox + const erc20SequencerInboxFac = (await ethers.getContractFactory( + 'SequencerInbox' + )) as SequencerInbox__factory + const erc20SequencerInbox = await erc20SequencerInboxFac.deploy( + 117964, + dummy4844Reader, + true + ) const erc20InboxFac = (await ethers.getContractFactory( 'ERC20Inbox' @@ -273,7 +287,7 @@ const setup = async () => { const deployParams = { config: await getDefaultConfig(), - batchPoster: await sequencer.getAddress(), + batchPosters: [await sequencer.getAddress()], validators: [ await val1.getAddress(), await val2.getAddress(), @@ -284,6 +298,7 @@ const setup = async () => { nativeToken: ethers.constants.AddressZero, deployFactoriesToL2: true, maxFeePerGasForRetryables: maxFeePerGas, + batchPosterManager: await batchPosterManager.getAddress(), } const response = await rollupCreator.createRollup(deployParams, { @@ -310,6 +325,10 @@ const setup = async () => { )) as SequencerInbox__factory ).attach(rollupCreatedEvent.sequencerInbox) + await sequencerInbox + .connect(await impersonateAccount(rollupCreatedEvent.upgradeExecutor)) + .setBatchPosterManager(await batchPosterManager.getAddress()) + challengeManager = ( (await ethers.getContractFactory( 'ChallengeManager' @@ -334,6 +353,7 @@ const setup = async () => { delayedBridge: rollupCreatedEvent.bridge, delayedInbox: rollupCreatedEvent.inboxAddress, bridge, + batchPosterManager, upgradeExecutorAddress: rollupCreatedEvent.upgradeExecutor, adminproxy: rollupCreatedEvent.adminProxy, } @@ -505,12 +525,6 @@ const impersonateAccount = (address: string) => .then(() => ethers.getSigner(address)) describe('ArbRollup', () => { - it('should deploy contracts', async function () { - accounts = await initializeAccounts() - - await run('deploy', { tags: 'test' }) - }) - it('should initialize', async function () { const { rollupAdmin: rollupAdminContract, @@ -518,6 +532,7 @@ describe('ArbRollup', () => { bridge: bridgeContract, admin: adminI, validators: validatorsI, + batchPosterManager: batchPosterManagerI, upgradeExecutorAddress, adminproxy: adminproxyAddress, } = await setup() @@ -527,8 +542,9 @@ describe('ArbRollup', () => { admin = adminI validators = validatorsI upgradeExecutor = upgradeExecutorAddress - adminproxy = adminproxyAddress + // adminproxy = adminproxyAddress rollup = new RollupContract(rollupUser.connect(validators[0])) + batchPosterManager = batchPosterManagerI }) it('should only initialize once', async function () { @@ -1119,6 +1135,7 @@ describe('ArbRollup', () => { rollupUser: rollupUserContract, admin: adminI, validators: validatorsI, + batchPosterManager: batchPosterManagerI, upgradeExecutorAddress, } = await setup() rollupAdmin = rollupAdminContract @@ -1127,6 +1144,7 @@ describe('ArbRollup', () => { validators = validatorsI upgradeExecutor = upgradeExecutorAddress rollup = new RollupContract(rollupUser.connect(validators[0])) + batchPosterManager = batchPosterManagerI }) it('should stake on initial node again', async function () { @@ -1377,6 +1395,81 @@ describe('ArbRollup', () => { ).to.eq('view') }) + it('can set is sequencer', async function () { + const testAddress = await accounts[9].getAddress() + expect(await sequencerInbox.isSequencer(testAddress)).to.be.false + await expect( + sequencerInbox.setIsSequencer(testAddress, true) + ).to.revertedWith( + `NotBatchPosterManager("${await sequencerInbox.signer.getAddress()}")` + ) + expect(await sequencerInbox.isSequencer(testAddress)).to.be.false + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsSequencer(testAddress, true) + ).wait() + + expect(await sequencerInbox.isSequencer(testAddress)).to.be.true + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsSequencer(testAddress, false) + ).wait() + + expect(await sequencerInbox.isSequencer(testAddress)).to.be.false + }) + + it('can set a batch poster', async function () { + const testAddress = await accounts[9].getAddress() + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.false + await expect( + sequencerInbox.setIsBatchPoster(testAddress, true) + ).to.revertedWith( + `NotBatchPosterManager("${await sequencerInbox.signer.getAddress()}")` + ) + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.false + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsBatchPoster(testAddress, true) + ).wait() + + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.true + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsBatchPoster(testAddress, false) + ).wait() + + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.false + }) + + it('can set batch poster manager', async function () { + const testManager = await accounts[8].getAddress() + expect(await sequencerInbox.batchPosterManager()).to.eq( + await batchPosterManager.getAddress() + ) + await expect( + sequencerInbox.connect(accounts[8]).setBatchPosterManager(testManager) + ).to.revertedWith(`NotOwner("${testManager}", "${upgradeExecutor}")`) + expect(await sequencerInbox.batchPosterManager()).to.eq( + await batchPosterManager.getAddress() + ) + + await ( + await sequencerInbox + .connect(await impersonateAccount(upgradeExecutor)) + .setBatchPosterManager(testManager) + ).wait() + + expect(await sequencerInbox.batchPosterManager()).to.eq(testManager) + }) + it('should fail the chainid fork check', async function () { await expect(sequencerInbox.removeDelayAfterFork()).to.revertedWith( 'NotForked()' diff --git a/test/contract/batchData.json b/test/contract/batchData.json new file mode 100644 index 000000000..5dd4870e3 --- /dev/null +++ b/test/contract/batchData.json @@ -0,0 +1,3 @@ +{ + "data": "0x005bbf6d343290db01f0e1f3dd39a2a8d7a49681ca5f1cd844a616ec81325a9e848861aad0504a3080ebbd71af203c4428c2a0588440c954a50850c4fdbedd22b074ddc12f4768ec93dc9fe7e7f6e7dc7b5f2d605488a3ac46f95823ca9c8889dfbf4918a495bc0dda02a3ffffc30ab001734635760f0b9036d6a677a2c14f41123440d7fc2c2d9a0bbca4252dad252e008b1ea075de87889cec99c81793315d93fdbceed9bcda444f8e9e8c8d891c86e9e989f9dce49df6bfd2f7b13ba41b9020c134d9f0608758a0c336f6eadfd4eab75f830d35e5869cd51aacb3639c763f7ef014283e72078fa0e61acb63dce3720de766196a4a6bc810fa75735a6c10900812b25843829ae0516be8c6126730d58504093e3c68c026260604c6505c9ea19613318070461c6b01489ed29c713ca07b0c080231c3bdf3d41adb83f7f0e47a308e41f4a330073c0ecf774437cef523f801701d78de9267a5b9fbbeafbbcfffb9fb9afb03b4e778e7ff567fa3bff3feeb0e24acad643a85225e163bb7cff7fca7f7fcce9db45d6896cce35652e22278d0f7fed2e79fdcaabff7d1ccf6e6da1f536d6666d4c691d61adbb6cb1615333a1254a25001a80454241bcedfbd07f990a864cf13f2512c0a49379754e30760a416b3cdabb73584e78a58ef071038edb040e5c54cd7940b8d5959d13aeec18ac4f86c251b6abeab80c5f695ea0e6472b46d7414e02c96032c0cc0c202b4929cef31a865abb7a1afd093f6653a4be96e515defcdb9c80481737f000c70a3383aa01224720d370014956dc68ac04f18f377bd2d8ce8c6585ca611e6dfafc27ba624564d939ce1a6b26727701f0d00560222e3d8ce2250904530a263e2a5d7ccd7b64150dabbfe8e107a4555197d64e73308826fa4e3a02fa3c36108e82364a212801c3505d18ca3eac076cf219c4b2cbcb6c81617740eb08fa5f8d18c4325840c21201901528e019a3ddf495ac27ab52fba5bc54ef5c6dd34b14ef15bd191932a22aae2fbb3d7b700aea45fcc968a74e5face61864a1110dbea4329743c8183ef033d0cd4fe8912fd207a1593a0e0b67161658de02894eb87a057bfe8e4409d4f339e2a2a34ef8553ddfc3040b97b9704f71f0414f3d1c8ef936cc70740725f08f0e56ebbdc67dad7ad7411a05fadd3cff6db28959b6b549c8fcaa2b693d4fa2f06c03ca789c0b597ff2f1b98788362af20ef0992d0e8e0c1e18051799d79b72aa7df851c9d1157825042c1b5042bb2ac3112b32e4a3c77029425373f76df7e86721dc871cfd21e45a1d6925fedd1ce8fbce0683da40b54368a5b960508f2d0f7c33652603787bd2f665ca56c0c5b76cf0d843800163290f81a4806c280d14500455311376b791290f087229d66f8b2a54c803a49872d9c8a4401cd2900fffb145584001f6c2c1d872a40bdd5def2c5964e4cc579048be642044f1b855506b0d987d58c67aca376d1f70f4fbafa8720b206421591e460a767583fb0d2ad8400b0d42c8c9fa3853b292a619d53f8b56e1de22360530b6773ebab916a04119a1ab41743f9010aae55664696352240a6c611ba362ca05c3bf55b007158a59dc1c56223df75e6674e562c78c36fca666f4354b410d285c594c5ef2c2fcdbe27cd58cdead8146866523677200a5352e84fb1f36821c4f7c72053904800c4934e0778827d62085b6c088042870243b14481a32051fa10b6d8500085ee7b71e0002d47489196721da54ae211a243ee4be97e45aa03878596b23d21603bf684f6b1db6f4b00d5d14b62b4a7eda75483b68040574fa9b18ed34f8b1763c2d847616afef7428a434e0930302e664d3f20665f3f76fb4dd72cf4f7278672a9b69b71bafa4fb4e1b2afbefa6ba700ba714bab312a384d2c52704ea61c155f07ee47e3a9180bd3c8447f033b25222bc7d634197941e5608157bc826213723ea4b16d75a79407b7b69f84af9bbe9d7ac1d51f2113e9f4400cc4435fccf991815974d71735af21fbc9e86631d5dc404bc45a18417c58a1fd33218027ec65270e095454c3f2a231fa17a0329681cb452fe2fbd3e42993f8e25e99bdac17be9f958626ea35fb1e5e44c7cff96695e818d67b8a851996b8dda2e2ead6f28a062e57e0d7043fd42ccce94424a4a1d1574a945c34769129d3a89ccfea42e71ccf90a7dbba1a8cb0dc0743d6222dd6bc4ffcb0ca2b782942e39643bb48dbc4368088ca503f16bd98caf373b1098bcda4550a4258cac09506eb80a7853558d853888cd0abf3eb33955d24f9eacabb044e912724942d9fab844fd102acfa77c36809b27fe00f3b70dc074f4a823db5d14f15fd71d0101826e991b1e4fdda76b32627b17f3bbc9d173aa04612626798fcb300b599c6b84d9f5ecdce85e069330651a7b9fad3040735c7331a261818084101279b92044b89533d4bac297fb7fb144b4664beb91af37b6b236345da3fe29d3364245cc8f21bcd9be5d7bf5d927313101978a694744bbb2c936ab4de755424f1b1dde81cf5aa77644f2f056e02e4b32576e73d8132892414f27a1bc283c55b05fdfca7a7376fa029c38566596fe52d056a0fa327ff8cc4d6963567db879602ee3bc2a0f49edffc7f343606856e76934e6b2d17af439b0cf39e077a9570466546a71414a4b759876b4ffc5a277899021e49bcd92a8e1cc1835d2095b880aee1b38209dd9073352e7470b0d9909064998d3593274b178cc41b1535fc35817b91306237e5f4cec4b088ead88d6a0e07ca77b51747d68a9faadb1fa754cdcb74cdcc3d89365f25ebc5a29873982ad2d6f09b8b448da5f86162f352c3dc84f42689b0a54624a98f04b92cac1de00236b585bcd9418c83c29db706275ed5068ceca21df5a49cdf52c7a5fe7feddbb9583b153d6cccee0b76c30517ee68891dc00742808f5dc85f1d1fb6f70bf2f617a64cdf4b081c8801cd66854903a38d8ef01b257a00275415b9b859322abf9c7e0632005411a425fc387435a2ee664a60fb23595f0d397743c1b6ed61658d684bca61642b11b7140b1590f1046b3f326d204b34951ff9f7d0a778a0bd68a22ba45dbe1aaab093247d98080fdf72e2dc8b24aa50e74ea35ed0f652cfb1afd1591445074280b6182a7e715ab3c8d42a13ead0e03ce8adbff7f3807b2df611c0e6881732030dd83eeff7271455a65df85ef31dd145b25b930c76dcc8740867119cfdd31407ed5643ff07e83dffa45903bff350978764d9a8953bd46c2a13534d3b7866a17c712c65fd61110bd1f98154e2b84e0ad4811a952683cf2e280a2da1f90c90944ae3c4b245b23021d96f2ab5dfa02ac12f210a7ac77351abcc5caf2c191807f3bbf76737cebb7f3a626a22dd4df19414cc3e4111c9878ca6a4aeaa393dfda07066c194873337dc615308d7a7e3c9eeb5c89dfe84311f8b0803c4a4e548b64423231201329f51e4c3289b3d406a0408341ddf60490976c491793dc8182441823092dc3320d5322efcec4f34a5efbd00d0a0d6e5f0cea753199615c5e891f307633e862b2bbd4608e47569bce6ac507220fa53e79120cc2e0f6e5c8157b5b93bf64ecb6474004572e1f8c8f23c1ab6f677de5f4f81d6718e83536800f2ab67e95823d4c6177c49cf0034791781ccf5ee281b15297a009c3702e335390c02aa060500d07b74c71808df53c357f893015f14a90655a9a9eda76e1787e8c01f5ee5b5f31095ecd99ebb79df05062de77bba02f10dec178699b79bea47cee6ad8f611c6d99acb5f988f11d1fcb9607fefff8dc808b64c190570bb51820135a530c0d241a20a3b3c31749ee6196c158133b5c7086fa8976db3ec0058b6e3a72cfafb39ceb09c00848dc4ad91560b9465fe1c7e5743c4412733b91b4fd8552ee8187ca9d3ee5d3e9093200dc2f1c422b422ca5f6c6921e9864dcb2cae716e0662729daff12c747377d02e21a9ea229a2ee9c07416812e5c764c7c9c8cf676940f963f297f20d5e34f4acd1ef3c4f8346089bf6b3025200c46f796b1435679ec4a3ec5a7feefa84115c53bfe61b07b35cded4bf912ec8811d502fe35c069ae88a282d13e6ce0a1a3d6c562fc3cd6dc4ef8973eac4fbe8b2a26b8ac40561d118c84c3bb2dcca1dcdce4334cc697a2f2d53917c33e6e5b9b8205e74dd771cd19b8c10c71e7473c7e5c6d2c5a3654baa00b14a4ab43ea35f422041ce716b1456043acf020ac00d79b8783e6895d49db6a73b593d0efdff611e235e46e32b8c39d7006196399fdd2bd9a8968c38408df0407061bf2a9e3a02c25261ddf374eda9381eec6cd11f90af742edc5dd73802861517c871d0b6c9c009e4ded8a7c157daa05f65550a334beaa25f4909e980d832b3f8770cb42a5908e20ef3ac39d8fd23aa5464f334bd1bd1d6f3757d74df0912109110f2a882e4df35c311ec243215b215891086a909748041b74154d0bed5114f10232122c52fcde70a056fb8a22bfb49fa812dfb4a95136f9e7dcba1abe2b648a4de80aaeaf5faf49dfe532ef7d75d4c1d9e3f855431a66703196987ec6c85a819336aebcc38eeba22ab1b9b7201459d0c0e313316bbd65c88b7dea55e0e7e08b57ffac1faf6858974fdab8968b251170a8dc2a61a43581040a180734597a9fe0327364ddaf5bc154745f631f6ce30a260efc1d5af6c3e1822d3056521ab83c7e1a5ce2f8acdcb48b44a6cbe6ec1062d43a4413c07d010cd2d50b2136355a8d8449d4a93cc026fd70cd32d1ff3a113a80f8280536d5ffcea07054dc26622a0e317d7518d21985bd34a74c6b05ef3fc6d589980c81a0673f33bac313ee0d7ed57e22bd0f33116a0eeafa8c38f093fca110156a7d0aa2db781eb0b21b4d005263b1498614c862dc3049b097dfb68db47246ccf1880924fe2e89fa1dd8e7b68d048904d359032c0088940527bff1e002b0ab3c0d73781841e5595286b365c23e1e95d987bf4e43e38b871a9644811670c78d9eb194150e3a908dc6450b7932b92fe5d49a763d27999961f602dec5adecc16161c80826d7ba25014b4a1877ee7442b38ca885eb1653573de1ddd881fd48cfe8f2c86c1b3b7192566d4b2b20daa92b7956aa25891c9197a34aca22b5cec452bf36f358321dc8baec0a06a360430c097f918fdf23542dc22e02c81df0522c4422a28f0c732c4742b4c2319272a861840b7473b07c5006fc0bace7c4bf23c6dfb9627d5411e1516b7fc0cb635e4f96737873283a99c266c5a3d8b0d1d721ef984d7d01099e061d829e96c967536a718fd9dfc3c5bb5cd356fe3f2e678f1d6ee687cd9c5b7d9d898604a2aa017a32ae2ca9f93f6b83c174583389afd373944d49320990d7bc2b11e0a6b71f46842c4d04d25baf81c2dc44d98082895f7e4e723620adc1e5f110f8f8b0758497f990c47785c113364527616d29b76a1985693d9f65ae6ebf82696ec7f071f8dbbfb0f9d399ed842adaece20baf60e67bd4406fa6ec783bcf187341a1b29ea673ff85ae602f6e72041a3adb2e83439f38f440a5131713374bde7bc2e3b239d690c08b7e4a092e8b013b3d228f2ab65f6ecbbf9f5a18f22876441b73c142ccd6f012296ea5de8abe84fe9d21374d15fbd278788f67227a3353db0053113d29b46f3b7de8dc01cb699f094cbd4f92d461f053e1f4f1d7db149d7f83fff832e3c8cb594e5f347f9314a681eae70925da03dcb2866b994cd5859ecb85258e059674f79160e74cf2df7adec95af7765f6f140fc77b15c49975093fe6e85bcc7cc008cce85bc7e2663b7fb24dcb17b544398ee9bbbecd68c5eeea07cfdf5a11a41c5743bbd75a7beb0f56e275cb0d577578480c4a5c252d2b44f81654dc8f74f1944853e7cb5d5503ab64bd75377700a8eeac9ed888a71ea6a351655eaaf0c817999f39c0143fbc4f5cee8e6f1f96aab50cb87ffa56caf4b7e68d70e92ed8166649e03ff1520c33ff7d8f7729c5cf3148a2cd83167919f0822b659a642e6d17b6323ae5d914f159bf152bdffe2d4030f7bf6a167614893aefb744b146c0d0a6931ded8feff6d5f12b618f7e24e3d62654482f0744af46465a749f2d6568a07085a6e3e9f998ce081852d78cd5595a11e25b629e93a067f9c7eaf1e19cb7868af21e98adeefd74dca54a4e8e32f20e816c0a018abe7bfbab57618f27abf45f295a143866648570fda32ac88baa991f6bf2d7bc04d94dbba5defca8fe00fdaed9c325fd2bca91392e68e5fea1b5ae13fc3d59795a1dbc4f79cc6f72eba25fa90e9a6edbd24c47b6745747d46f710a54dea8a1985f56104ce74a5aaab10a68d7c569a04cef67d0805df3b42f3312ab79a2cff72a53ed1e3009d31d9a709fc1bafd3999d53d4c940f5e7bf10ee6e6bd34fc1367de4f2ffa58b9fa4d7416a8c5aab97ceb76dc4efaa14576e2e4e2f0aac694d4e23b3fb5d4b2ecb14ec38a2d6e8659daa27c1b445d86f24007959efec1c62cb5c8da7234cd879c0fad04244a7bab4034baba964b990d7dcbee1b56660f9a652275eaf1a43cdf26e393698f4a68977f438d85c06c0006e65b6fca669f68116bbddf135f4008831f45672c3d18cdbe05db8e7492c5b7b003b12dcc85e9d44b1e39f34db56280fc0f6fb1571ac8c9135e107cb8f2f64b950176c872d41ce2bf64a6e72c0a47054edd5b6280ec9fae9f4a3966232f481cde6f54a0ded02dcbf60748ac9028e6a9483541053ab5646d1d23214594feffa0ecea12dee58abcbb99b66cc08b0e2807d4b9a7549a622284d85104bba54b91e12d847ba9e80f3cc632c38e3f1c3d65f1e32e84441a3be8dc4e632ab92b0a2b4ed4fdf09bf203ac474f6c76dfa2f41cd311aa149359bca8cc5a10ff91ddfd5993e25ecc3c9b9ba766fba1e3e0dd00ac6314883be898bfa3e386e15a1724f0c0fa7fc56a8c36c6c3bc675691fea72c33d24c8023f20ee60c51667f3636da940a2275fd5d64d34a6684327391ea47f6d83b75ada94efb39a6787251b735b569434c5d77f2e2b634a87b839e6874ffaa401ad4dad8af63e28ae7b7d1c83c1d0d407097cb3184ee57a7936edf7dc2068e257e523d6469f5580d44fdad5b4d6215f00b54d2aa0836cf53bc6c8d8d6453b0d85591c6bd3c25e10bb1c1931f49ea122c069642260cf2f85843368676580d6059fd0426c37304a969582f0e19ecfe9046d857441cac6423cf9648c2c88651a6d27f08e13b733dfba578f1f765b7d3a00c570edc08a0a005a284083a3207194f122415aa92554b1c10cf51a2920c61d6d631fa9e57b8034cb2f1e0d58fee3ce8dc57f36877047e763dbc79efadd40595c527785d00f136cd3c858bd8079dc4b6bb40111f0a2cd361d49b9da4c39e22be0f1f14cb896d8d4c24ab2ccf449e41ac6552cbbcec5e64eed108fc328e695325b993e061e297bea399959b36c707fd50ae0064844fc8060e56e5870a019d0999bd16f4209b902c52788f78f2b2a6dc8a1412ef7b3073bf1386adf4e0cf608d345b4f9cc589f61fb67aff2d67c0af5a4f37ebfbb0f9cfbf5cfabe9b22ca9bbd4c1f1328dde181cdf6d961b564fe2a67c778acff639c91959706ea2234577248653794c80fb4f00f8223db3e9dff94df7f0daa6ca16576b9ec1c8ecfdf9650b2694c61b8add7cf81fe371606c91845a7a1cae329e0d08574382b8e88da254824a296d56c222fe6c97004c7d84f7f5516da7e27685a53c05c7356bce08c2fb4b598d9715d7bc3de55bb77ab7a2e0913a7233f8417ec5f754ce7ed4628ca151331c2a65b63a5ce88a414cfdfd6afdbb30636e16a6136858d600e7d3810be8a9003b0a7fc76d3eaacb302870dcadc90816b7f2f51cb99315ac3653f42fb8230c0ba14affc3ffcc4210157b2969a4755eddb0e41455f81dcc3ddb7a76f4bddfe3bf5fbf6666dd10a0a6ef2328750702d4cc46fea3a6798c31c37eaab2f3189b6e46421f882dc9138b387fa954a1f60ebac0fb41d494a81f087c3e92f38b41e7ff1f222dc2e549065ccc17bb4ddd6d8d6a259c944e15d99f52eb22000e410f267ba77907a6c07e8fd117b31782013d7d3655584f654e06e3fa8269581928ba9184e25c2632ed4314d6df3fa0576dce9cbc1c789679687353d42dbbbe5d56c1bfe5784f9d6a7be8ef90ce41f9e9a073338660cb44edbec9e24e5f9f8d7ae6b0b75d7990ddc5ec4622dfc75d68b6e51af2094dbac2549f7372de4591723bdeaf398fb2652042c6293a690008f4ce1826a6f8909cff4215146d2fc4c42b8d081bfe19a1798ea23807b0cb27d2822a28d8b6331f2f86eaa60f2d15b532cb720a57112465dd2f51ed1776ca10331d7a8ef6dca9f65dc25c352c1bac8e885623833400daff5dd7127e66297d71b12ba7f846d922392f1d2513c762fd61b60c70049cf085694807d19b467eacaa641f3f26ddefd45d17e18307ab8bdb220e94ba6f71c310d72666d07330905b8a8c9d54bb09e59d052b3f26abfba9c5ca2030dc63d551835498edf1af43110e7a347870a9fc60d97c1798bc768a1637e4a17a839b6cc893bde080f9721a35b6378c1855fb09e0a2a42bb43671f1080564fc0108e20146441d1f64d8fc9625df9339345519626a2bfdc66b0c1e9ce895319e1a7e3b9b50f9affa7f0977e034201cf06197e65ec49264cab0d94101c86debbd653f3f59a2f206b5def04ff66d9b45e05c6128b8a6b2eb2db3de2b8634e88045dad49da5b71af52346b3b2dded902199b9068507b5db163fa40b3c788d7d2ae585cca55bce6a8fd0193f6261a6da27df43534f80d6e9f58c1e3a06e900774a1c8f26f9eb080ab250ea3de499248dce7d7f23025bf650a5ba91b5a4432519875ecaa23af9f32aee41561d6036e057348e94fda23114df0129002fe34719988310600bc70ccf20bd57cdf1964508031e87a64f56658981e2b204f04fafdd9746b49467a7cec377810db11a7659a46c63ac5133bdd956200bfa9405ed17dafc90215464f0848aafd10ed2900ea5e46a9bac25bf9c396fd2bd020e9e6e1379f42077289a53a13faf8cf759de8a327abcdda1a9cbc4499b5bc91c7148e32cb118000392536b2f8a02228ab58547d0a4cde4999f82892ba7f6b81cf45520ca6e4aa11c907eae85a705e9f55db7d462d865dbc20c075685a903c7218a45323ffe5bd5764677e9de97567d9feffbad2a8fbc0ba76da380193505794460a047092ef06a134b4521000770d8854897d5a90e2c4e74560997e24578d784175d893eebe06f43ec8bcaea000f33cb3aaf9625d73481aeb0cd8a63489e21328ecfffe9c267b0983922b3543155a0c0105c1fd201657a93aea538a722281d3663e67fb4524c5001ae122076e07865865fb61e8fd481c6f6996f7e803b4c8d43e4d7b167ef514175dc6dfa5c4b96d510c29637413995e48cd476e45f0d2e5499c904e4b2043088416b2971e21af2f0e1fe43280c898e8827a26f9fd47cf9c124cdd962778a42af2a30381b13e198d6c8d89ee7dd12a24f4457c7ac8c42634328a0229eff0a24cc93a719b7412d51815075b3e055ccb72c34d341022ab869dde915ca76a71c3174f174e643331a000392ec7a3d1750484aadd56ea6652688c366eb12f7e7fe4ffff7d554dfa270d8d406cc1f11ef63d8068c1729fe5f4d6dce9e7972a7012ae01ea0b7105f823db3e9df61505035c468ba66fc41cd9a5cda1cd825ba1970dd1c1c7fbda5bf213a5d667ec418428cca2725c03b40b62cd232f11bbe363db9bb33ae2812aa3eebce86cfc649fce734120fb957841ae61078673ba4ef9bf530588500db1fdd9b060ebb924b852c70e25a2ee8fb1be8d803835ed32d71877f875aa9f93aea019b52310696650582eacd3a835ebec1f1e66771de5f79a34e6937ae0e443ef0b1a77d9fdd56d0afc4929c5e7a84d1913077d604dd23b5de0bd4060f2b833e6958b146eb87b247a79f25c15298c969959425dc7a2a3fc20cd5336dbb320d5ec2cf0f3a9031636097bedc10f540f9d913b0a8dae3f583b4a1041540efbb1d37840055f24716a6f50f68c1dbec8bae2e0b7489db1b38d4703ffeaada91f23244b6f48101845214ef906390b873da4fc372de7609f9dbfe494fbfd33346c885adc48fb55a00aadf891f9399abdbbefe65f878241a4ef26b4359f4d2896657800d22eea2de9ac58bd90e05dbcac3ca1ac54316436b45fd8e94b1cdc6e63870465f5a247927bf51b4d0ef57e74fcce9a432699b8204ba7756415b461bbbdff5810171ef86e92c02ce426d1ef478b4fe9888b55a80070f2d03e2505cad497174b49e00964cdc014b1195de597df1d56ab78a736d895f0a23e26aca3bbc0107e5f2874f1758180059d2152559df125a635499830c4aafe6173253faf71ba20fd5c369257fef8b4954d717e412d74d5fd67458dffb80c70018e0b01c754b42eb89e7fff0bb1591b0ad0c62b281feaba32c0937a6faebef03f45200cb1aa6c66cde6d7b2574459996f5253f5259cfff9e36469af35dd7ca9fa7063b2fb3a160dbd674a45d55a51607d7429bf19d056f8554202c96e52c13313c5417eedd30182b08595da78c0cdbe8846f27c63e85fda23f3b9a207d257f397daf78b9fd51b1f5beabe66dc88851bd1487ee0e22f9981f69d95271e7fa36c4aed152808dc2a0b911076e23e3c23272453884fa0f8f1a4abfd379c84e180769db2a5149671b60815cff0f1526ad4fff7aaf16840565e40eb3bfbad7dae4c66d5d1d86424c6f468eb77b8177d58f81b79cc1ba7f10374805f799932c209651937e1ae30cd8cacf01baf65c0e58a9b9e76a73a566fe71108636e7cf50890caf6ee1e1c7fa7e6a9d71e228157276b6a073b7ed902e1053f8ca9c99168a26f825a6a4d0d76170208b37598045401b79c1455c40087e51a72b40df87c942be79f51c394c7162bbf3cf8cc02fd7ff156522d8c86548108a598bcf3f7a3d9c765b835fd4d70fd674ad3c45c026424e1cff20571b522448c32fcd6b8f3a760a4abf6c8f7b2462e72fef3c070981915fc20664e77954e960a0327d0bd2c7e270d9b68e26312d3fc65bc39561673a32b3349a77015a5f82f3e1d9652004a0d2ef747dfacdcda84a01ba73afee241be7ef8bc87082dedf1f9aa64a862255a60051b510fe43c6d1d1d87f57031b6b8e9b26f3c1a539039a9dc5e4407e88f650d32ec87c1363cf0f17d15e8ec0337d4718753f019ead39a1a48b90ebefb0d89332495fd4f5b63b513081f9b429b32c0471fe80d17f013a8ec3a324b0b14aa575ff8b34b880d45d000d8d68bcee2f5cf267b5bf7a834909843dff41556a4fda757b7b519163eebbccfdde3b42484eea9e274f8d5a922c9304ac76fe4529687f4d82e1c085329ec76afff13a91043b5c98e0fa969147a35031986643c1ef7cff7987bebc3ff908753ad53f11a93dc5ad3eb7c287fd6dccb0355416a2bee72877b2aac5b78379b27b79ad1fd1f652cdc679bd2aed9615762d77994180c90218dc145027a3fee7174770fa376f56460811e77a54824e9baf2dd932c300952d53acd3336759c9117dde9f59146379199f9d4e80c596c337d986509a1fb82c4e7b521fe8e554c0748b1a2ac2a60ea4cdd9dfe19e02b10f4e43fbf19b07843ec456f8e50c60fdfcb0f5fb7434f25b67e1cc4047edb460cbc111008091e421ea721e4e5b70df0aa95b26347e1fbd2d0302409e10011a22a150ba44a30750e4400e26a120048f1e75f0779b7cbffaaa97576859df9123481548be6c59972dc2ae23a7f9096140fb61c837d8f14e5bf8552625ff9d3e9bd8b5c879adaae5c99b814726edf9ec1cfd90fca777d90549ad5fad45eb3a398647f8fd16f882f6de2e037c928d134aa859786881c72fea2c60930886b02e78ed7328eac7a5197816eff080efed964084c501fa61f4d1f9de6a2b84b16e8cfb8298dded3114f36f5eb103c56fd1a1820eaa3a5d303cb3fab35e43733e0b02dc373b87c189d56edf1fef3242382ddbd6c0ace88f4d207ecd365961c1490bc487b40f0250b92d9c4374c5a2e02f8526378d76365cd983ce0ad8d9416181b8ac9c5b234062652aa5f6223873cd8d9f880e74aeac6e7f4bb532c1bce11f9962136acd1f7f9c989c2697f3c6477a60552a50a27a0fd2d3fcab4c580f9e00e07c0803cabb14844b7ea4b9c92b67cb2af3155a95eb0366e36a64fab7afb4ee3ba4c5a077a6b060d11abe052cc285c542321aa8df0dcd45bcb08beda64d9f288d467cd9533943b251a0844bd8ba7f815c2a74828080be800960047ad8d8e3a47eaee002c71b6bc9fdc74a001e6e5fea6600e3cd26230d25a59eea17d16fac8d2d40936798ecf8fa77e5e8438bf8401d820721e1e4fedbbfdb7fa6fc9aef23c21d1353f86744a0f2bfb517a93eda3a650663f18e36e20424d994f2a26f044e49cb7bf753a9107f389c704342b33acc4ca06647e80ee2b304f2b4f802359586e5ea7459bdef35209f3a890b19cc88b01b6689838ebe48b5fbe2264691105299fc5b31bb78142015607ed57e5ca19174c0bd3e9bcfa4fc11084e79972f09d98bce40a17510ed820cf2cd1ecad85654e55c9f516d5a7b978ca67ff265c7c5af243f11794b897c7c5a97c278f9ac9260f9287865532932adcf2f26d97fc7a2ad38080fa1eebec66847f2ef806a2b29b320851ab27d126b33f429181b5365a935a398be588097c1c7c525ab3d591262627a48829e46524fad8ba95f14db3d0c0312b05ec6f6e858140ee7095fa7135d90942c7c195de64fa7ca0786dd825af5be0080aba806556f7f97cc2e673cab6f2f60e8b8b48cf1cbf1968f6bddcca1c83e121b522b36403ca781280432f6360829ccc51302a367f246275f3cfc4c2e37aefcfe5e8411f50397ced95316dfc4908a7f8a6021f106f7d8a81ae565af2d3821f08875f020de47b959543955c72c9cbf38037f6d0a6b85348f6ba71743aed8350429c108f4e36dc696f604edda0a09f5598c16e8936f54d86f30197a373dec31388268da128420b2c9c908861df9439e35d0b0ae782eb48d864c21e45aa3119201945241cc9c33cb61817728e40475175a1a86dff0bf9db386e1a2689bf6bf85073125baacce49782ba6b4c817e733cf2b983a7ff30094bb1fa3a7089c1218fe8104ccbecbdf9a6a28e1cd446cd175c62c02b924c207ce6ed950b06d8e21e63f1e2689445cd9d382f2401c28978010c8fa9d75eb32228063513d76f396a62289150f176d1cdc10f29152c37c1c1296b8fff8048318e81f8c7e718e0d429a66909253827e742004e82f34a03a94a2fe7b10f73d9036bb99cffaaf0d2fa565408f0e0f7c804f66a10508efdd8694699ec02098a22a0e3954d3e3195debfabd984b2c79dee2d936afffbccabd18cdb4c6b27898b0d3a6cd71da88a00465a9a3fed07f9710efa3b94e85dc728e1afd8623c07a5a6cb35978b35cf70db3b5ae9eb05bb9040fdc87f56617a83051ff16b608e7ead8e37fbe04b4ea4a04a339db2b3fa0d33bf6c667e38df341fea197e7770fb8ebdc29ee3346ecc254bc7f96373473384722dde4222732ebab03d25e9ab81ffcff33a683cb2b12dc5dac00a828128b902e10f48e3059f65940009d7d46c12b5929a27c0513a6633e37baddfec1be0d269baa9d67d995affc005954d603bb2cb8330d2ccfbf9ab94cab5fd1d78ef1b52bb05f6f617ed77689087b28f1235970ceac9b1d7dddfa4c3c88ad03d242c32429c742ac2b74866c499e900037e1635839f095d1360bcdd37abed4cde9f76211999e875f7cf69c4583a14c9931f621591d6cf2ee99819f8fec41518625ba237c03f1bd73509d763f65fecc8484fd0b77e425bc9a94dfdce4865124d71e5f1d24f67cee9573692470488174956483a048305f105863bf7c7eb912b708c9f6a506c672c4b3ba8f587cdf4b56d31a5854a2039df134f2293cdd7e4f09c1dd5e3e91aafc5d5c7c7fd31f69ffc399bc84a55553df74ac92bd6c3e2cd9fecd78fdf036c0bcbf576fc69c445c111337bb2db9c329cf3fb348edd16a2d7051adc8faeee19bc90e1067bb3772915e9a3ee3071abff4584b7957ad7cdb605e96ecd5c9381c7a69985106e62b768c193f4bb04ec231d0e722d01edae6c2b159cf3338906effe241587545ebda48c28242996b574e36f4604265bdb0686c38d13b0c9fcba672a3ac72ab68d89e91808a87b05eb078bcea3e572f5e2d23f882f23e25f87d8203d45a0235cfdcb77c97e4c9c7f3aab3b72d9febf1179f0766403e01407eab864954f56b8a3bc777b1d5944538bb2ad474c722c4daa8b96895bea37a1f43c9ffa2e449625e3adaa481b976a1683772cd070cfb7b3538ca688be230c10816403493cd57407d8d23b7418d8ea26038cf93f10a37a2326b700f8404f83621510df1953182f579b4a6a41bdf3ecc7a645ac77e385cdbb404f5eb020e45ed59b95be5a2a828996cd5f8713797142ef69d64e5f70e34f5cf548123cd8423f251aa7fad7f60bc84fdce2afce7cf2e29a0b9692156f00464f97c0eeaee3dd4032a86ffd7d5ae7e859bb103a2f0e654b0f817b0b30af49a193a032fbc7dfb6c0df2ba162d5cb28f4827deeee6d0b1d9a93f824e14ee3ceddf0b341df69ffeae831eccb46acf74ffb9af5e9146d78e7fe640d9ad66d4189eb8f1de1c965b72f2c7567e50d95a915c968b067399143b03412ea1dc70bcf5cfc96b46fad05ddaf9025777862c8e951d7876b97e95a8db0d75bb86647b1aa847e2bd274536b627342f1ff8630a0efab1b80187705ff3cfacacef5f05fe2c71d3860b7f389fe50033f42e3bcc3f59916081f892c94bcc8442c806272b9bec7f7798be8f01d8b0dabd6ff9a9f193e6e0030a3572996433f0a2219c8caa83ce2c3137a12c67e782d0bd0eb0add2efab3ca17ee3dbff49114615cda060490356430f4d449aedc9544146ad86a5f182bde22598a4bada389dc82cdd48c68878bb3aefeee10eb578768decbdacfe5d260b284a3508ea5e3c9da7e466942daed76732e5dffa1951baf8cb8aac1753776e9ec5abeaaeb1b58becef3087db1ea5212737cf6506a89415a568a2a9d5bc61abdab0e58db214e80a7d0c2a8fbcb7ca7d572b61063aa012af18b6e640ad7338dd968af9248097d4368b8056d9adb91294ee0f03dccca9b395d7a0d6d8dfca43aa31b20444fc74d3a9b6001c35a1f05eab541c6d015fac67829830dabbb6ac41959234f9dc1f7d68eafa765d909feba3a162f666b80bc9950b65cf456396e4e5b6a4bc3e771a8dd38f0609ce9573f1c08cf711b21ba2ffd7565eef1f98e331ed3f144bbdc78d5c0ac04c9dd34cc2ae96fbc70ec41398134e1e89fe065e2f18ea232837637d383a4303dc6ccfcbd81597bf3d210d7e99b6d3cad6a29a21075c622fe7c3d86ef61bde52dcf3b7190f4db28e2b4a302e966aa5426c2b03359fd0badb219db6b6ec89ff495e6e6b402bbacd60b610ff8f989a7e64ae8c83d4c52f3c175da1330ed65c69530e0a3f23fe09f4540ccfc26c1d3c27d0f7ad3cedbe76a46bd240df4c0229b2a5cfdf92fd1e842febd9deae51463cd837c180751e4caabca2b1e0646accdf1f93eb21620979a79ba5d673bafd738109b54c97308ccc4e09433192e9a5b84d8f0c6408cc13efa1faff20b838b4c36391e118a741d0650e01a34676a0ff0dd313c9e9096fae42773a8318aacd9ca207f2b2d69869d2c2b7be514a9066f5dd19a7d9b30d9dff20ac505ce8fef061e8c5cdf4279aa9749607e1152c85b5cd772e42e83d229b295922a06736d3c277f706323cc110829d9c58a560982bfabef51c1c76967a38847496d5fedff786b238be8814f22aae2f0d6244913a6f6ccf9b04773007e25f662dda74666c110cb4a3900e0bda023cb9b2dfdce78bdb54f51209be26781133ebb2ef4e170bb9e0bc7b00f96c2cd2edeaeec7eb2dfe09d5a0e06105c020a7e516b5cf8669c69eed5f3705f5baaebe16f8f2ad1e996bbc622cdb25bfa1bcd79350b29e9aa79786b7adc8fac0aab7752030710cb0f9de4d9721f00bf2e33cfeca2d0acada4baf295c99481fef1cc0af495cfa25206808f805ce5ebb558c3067b8c49fda698dfd3bfeff05671daf597bf1b7eb2d3977f708d457eae3bc48ea9990f1b7cd050a0d10cdc83303969411fbaca7f3acf99f2a03ee44f87eff09e98ceaf581ed2eb10d410e286f6c896c2aa7b8f9c41152108058276145a0e244732d5cb83e5ace94338d095b2464495a5ab223ee4d110fec8c724e6d2204f9de16e3e7089dfb8ff3229555171aa9db3fc8e4563aabbbc41bef91079b8998601da8d7bc324cfcd93d2e1c3ac33002e66b4c2402a57d2bc35de5475d13706280a9b469e210f4b887c6243769489c0c24a8e5b0b9a203b9f503ef438a4eab4f16cd9aed07bb5d16144604e307e636212f9c566102916d6902d0a9480b6112b1b6f672a4a8d67a367c658d064ad668aa72198ce0d6e0bb3b4a68a92c5c783d2123a60bdf14ede1efd23d17c48236fe21462e6a7aa37a1b06058872ce4ac87df4072f3e461ee02751175c12457571b9abf32ba647f74110159e7cbf474077e3b66f56f1bb2831c17745356f414bf07028fba8c9ea598d7035430319c0536b2366da4a1ad7bf4e3c8186b8c618f9f338237bc537a19b9a6c6387b77a3d467c49196956240b984e641721baa1382817c4cf0d5a708a664adb5553db35f73c18053b84a6acabf8b818d205619248c043dd6d2e04191d1565a569f11402d86eedbc315f4fa7bf6cd586433e61577c1202ef1c6f3cdf48047847143dc12bbe783b34fada726dfbd1d3bd826f4d66a74ca32df7dd121382dfd3a948272cc114583f7b49c81a52347416db37c109edc82dd220fd4169fdc00ce42f0a9696ede246fded63bc1fe8fb5630e614d0fe77a0ad9f2000aeef05110026b67f7fc77e5f0583d0725fa6a2becf4abf0fe1bebf190000bb5e60750eb0711674900274792a288418f4f4c9f77113e89d0748df0e82bac74fdf199a206c4a406501a23501a9d783f40e20e767b0710c1fc319682abe13cbdb99e38731e2f2fca1ce6f19301ffd92a754f4398fb50b69e23bb95baaf7bfc3b008852fc315111ce9c3c35dd7df25114f791f92317a20696db92fc3b1ee01b426e4f8e7e1b8633f05eff0de778913bbb3c4c4afe83e69ce3313cc16f90dd3910346435f32c234e6666f9b8b19789a5195c190334cfd2e71c73857222752375745577411120918ae282bc751f5c35b55701667d0d5291de363a22df8437a60a3376a5ff349089dabcb7145f33b0969697364337ea4166bcc5d19c598a480140c6a514e825edc366b865821dfd6395dbc2e0757233ffcb7ae05844890b1373624a566b1aefad63b0c496fdfba3a5d3fa407f77c21757aba3b769bff106cf96902f7021ea027a59daba60527110267f47e6af255e685143cc083fbc0b9c716227eea7ad9d3054118215cc64311a5b92c02bcf91441747116f2ea884d386d444896638bb40ec7bb5b25f2d443e4eee1686f94ddc7db90ce5dc843bd067384249dfacc8286ec78fbdd9516a9bd58e1f2829607b406951269931a26ff104c59677b9b5c99dee045e9f834ed8b27dbe889e3d8b84eafdf02ffd54784b0a19d440ac3bc3dee22e701e09b8c5fea7407392ba6a3cf6008ac6cf5408f975828242ceb816a2e7215977258a39151639716242d0a235f5a220225954e229c11692f43dd5a7dc236738eadc6c2673d70af77fdfa49b524a47d97def3f50c3c7f7e160800021408980a18b4797793c72f229095f70708fcdd7beb517105959369b3e7cc10b5b4a15890da60970198985f8ef8688c56d9767df44d61720b3ea2ceff252993b09ab8d7d693191d9d8630e0ca48a52ba836d58f6ca78bfee98110a0f41d3c5e6d197f5007a0d6488f9c21e00ca5a11ff758a430ad8f511f6256d981c4cd48f35d5b590a90352d0a355d3de2e3fcd5494aeae2021be4e2c5ea4a4a9fa8406a08608015d53326a5af3b2f2d7c0f34e973909d244cdb89121e6f5762357d25d7b0f43e40c964ba3ebb18a62c11590f27acb52a83d1e50a1535ccd55283d2686e4c974355e09c03116f90c62097676d79bee0b908844f25c5510419e1c524c200eae9dd01827a29ebc37038c05089a030c1481bf9cb9248cb7a1a7755e6400ca016d926594232e681d452e9487d4735ee2f5e08cd357e27802d2062696d97adac19a9760d7239472aaf41338cef8dfd318696351002ec00f6817788ce1d30dd175e14b5c440bbffaf13363786e2219f48a69e6ab6968038f92bccfbfaabfc16c4a8b37e5ac3f1db298cc34a0f4f5e7efa6d09c794fdd90ea62c28ed6ff2ab1af32d82161426273048490daa504eaa8c0e30de9fe716f70af97b329b69c7d3f6ca849556f4b9074ddeb24dcc6c5d0636be5951aa663de3fb45cd0adc05b08512856fd5409fc00e73a3606a07b45a49eeebdd2159324f3977409e25e3a27344a5fe50263ddc439681ae78263bf11c7fd6eecbf1201cb9eb7e87668cbb7e403c6dbf100ce202b767793ac8937f204e2f7f540b4d9689a2938c06eedf3e9ff638e68fdcafcdc7465cab1619429532b5bf6c27a4f10c43388cde8600fa8a656dd57fad2bad8c71b7946acb3e43fe455422d98ac29c6c7f61a14ac0421d60c2978ebee825330cccb108ed370dbe11d9cb185f09d2a5a318a9b1daeb1eee600a6fb4a2a3e2de6777585ebdc0f7d235d82c1330381557cb0807a0571c62d79f1fbba47f9e6014a92ae47d58186600b327f24ef7bcb8ad1b908b9339765c74da5f53a92ef31c9a93dd34991df7e233b66007146cabd026a77fb176686989cf193daf8f083675135d511effdf51130ea90d89610cdfa9ced3b73bdcbd065d1c7942640f883516bb0701a4b9b0995cc8d1d27d000cb0234ec7ebb8fb3bdd6d0825d0faad7d4c6fc01f5f4ed07f1690d4a15383d4480fa0fbda27d04e54a8c4d2e4907411886caee64a5dae99a35c3fedac4acfe091497441cf81e8e374f754b4b468b38c77d8f501a4f25d50a9f19d13a5811b0d2e1d1b1ab02e28eed08911edd3912b04ebea5fed1fdce68c2e8009677b875c6b5485cc8e92259008378295070b38930e89551191033ff632dcd34d776010567a76ec0d44e6fc38ad12e88c5b5e087480d546c3bb249436463718b21d5dc59f2a8efa7e60f61ac40f0fe05b5717426ac7d080efd1f4f909b11f3bd7e47c968e58a48b045cd44b544edccc8c63f3adbb4e2a68a64e6f57ff73e449ac6619f6fab011d8f79d6d7cb6e3febaf27ccb642142d8809e44f214fcdf7a9d5bf4e0bd532a365bc0ffe8d943f03ba6cf4837dbc9dc73f6cd162e6dce00f0a1c2192bb7a834a5fc605cab8813514cd590fb97d6ce21b221c3bda3686d1397fa190372886f50823c7df127befdaa893ed9bbe26293fb7141846302fa05028857d8e597917278d2279ee4f79cf8a6ef303e3c8c36048c048822f62d42b1c194e8f06a408c3b31c7988ae502b62efd37e89014964746c22c4f1f0db13cdf062853315c1f85bc24de5429e1bafcd8c6d8319b79ff11faf1b33e5379d9691c3621e83f0ae7a09490803c6ea1329160fdcf39ef283fc23350c17dcaebf6351df4cd5b7e950138292deeaa8dc242920e889d45d4f1be551d42cf371c8372488d62085a85874799b5f0233281222ac896d31012ada9dba3afa8f4a1e1fd95ceab8d548b1b6efb0753627ca3079745ca81f5e5adaed7ce9239f23fc8dcbd31472ebf88cebfca1ffd0794f11f929a3a7622a7edb7b73dc664c0cfdfa6d9db5e9083f5bec3c3897dc479c8b3eaa43cc80882966f27c4f896a828d52312dbdefafdce8d48f4957c7d17ccc71fb7e78a4a4e11655692524c06453011ee6fc2e57de3cc7af7272d6890c636d5d8c17f64f4791aade7aec7c74327faea132827f574b75c5bf8d090b056134bf577afcd9d0240c1c770b2a7e0a841c766250b35ea0d9b3340985bf57bab083880d3b393c52706358eca4ac3f7f0e3f2f50a19f660b774d4c554bc7e353b7d7b49b3ccf97d17cd7ca4110c47c17b13fff6a715055978cf592db3955360ae22d8fd5cd4b2011054f21ce5c936871086ab1d02da256fe976964cc427a5b31947fb5fa2e8ffaf53b8fa366c5aa8bb0feb79e89b9caff4033124d73d77ea7acb7fdf48d33f0d5a23da46f13e96cf5fd7902a18f7c6dfdae0423e8de002ae0cc6ea6850dbca01fa01f431a446d6b7d256d416055b8777779adaffb4e250c80cbe76b8f6f4fcf010ed95083c2904d4c75b9d25cef27fb1f19af8f9b26e11bb98582b32fe6b3e3c54641a7717eb4090b33f60f45f801a41cf7a9d2cbbd6bdcff73e22b0af9b2c49caeb7173bc12fe1a2f1bd3dfa72a3341719d72f3f9702d92195cea97d95e3ed58a7cc22747bef3fde9561d8a1cde24686caa1c6409d049636b20e946b6ba9f99af2598fb39d2dd7212892ee472f726d0b31adb890c8d452b119fd78ee81db6723eaa7a451aedfdb781e4488dae2d191a219ac529d3249df695349ba08575ffc0a2d95952b508354cd549a27a7cdfed11b9e031606d669852fcd88ad6e9c4ff7e6450a1efce55f58ff52478464c3bd078bb2b3ca40bbc6d3e4a4da6c6bf18042362282812b4b6acd28c372ba9e236ee49eaac43ec4add32073ea8f53cadd93d06bc1a6c228506dc0184fa86d2459bdb2bfdf61824eb9fedeaff9f190a9b79ddb2aa366364bc720af7fe0201e848c9f586fd99d4b2f10cc1802d07b789c42cb36f834899e5b1adde946ec0dfeaf438fc9272811e18b6fbb5b36fcf7e3fdc21501c5cb7cf9e5af947e34b895758da54bf2cc3981762617745dad181af68da114cdd118ec88cf2496a5b5b931ab1d3452ce04760a8c59ed06fa685eb5ab6361ffa4e9cc8662d27d5afc0de93d3f8bf93f99a93f9a3b08bd5ced8ce03498b9f4f599403db16ddff137c5813554e8303e41c1eb78a4527d3649385b0f595f7cfa7c22d4bbca18804216c80a9939546a6cf32aca25c981d511f72dc90d68185c20d7746473da5cd3a02c111f8d21f93828a0f6fed8b6392fc75d489abe1079840f955955fc0cc04428217512f9ad3c4e37fdd0360c0981d8da79d5cd1d8a2092546fa8874e3079adf6c473f03e460859de07befa739c0be405cc3e35d98d96fe403f18324d33b8dc37c582352f9bb0958244d531ef1ed024bacc2d47eaa5a04f7edc440570fe2c9fb2f01ffb56d2ef470ac6fb0a85fd8a36c513b4c0b501dd67a12a7b131e543fe6a01ce32c72d855e103fefd955c7e36297eab18943ce8288cf0e6393fc900e42504e61867d336d558ac6790524596d8d1a15f3a397b97b158527b4a9d61c6ef63864131a39571663040d3df4b4d3bbcce363eaa894c47a61e8a779518b05eac8661e599621a0f7e20bb47a2a150536621aa589c96810197753a39c99da8c70f9701b85c76a75813d00a445389628a5a3d52e4f05704c92279efcb233e6a086f2428d19f9ac94b1f03a7d11a1b81831ed34f5f392c0de4857a7b36339699c618f17a6339611f5fc4fea91e8aa001def0ce41e02908cef4c0cbb3c895a4fd42209d3a6e394fc2d221cfdebf7597a729b83eb609a68395049ac16216fab06595dd5ac932afb894a9b971067e3d738715312e8195b08eaf345d294050b2b3d1cffd0df9f8cfb7293116259188ead3f358f8e822067a2b816e9bfdc3130316228cbcc11b56ca31487210a383782fdb8081b164d448e2b8ac1df09c7f7e3b4b086df9ef6e62c4a621965d546caf609a6f03f265964201aede980dc8d1324f781017b67a401b8f260465353fe7b6c274e897848f401271aa44d562430ea67811a1d00ed6b67b04c1c233a7abd5707783ae78f544e8a8b196ef2ac8c890a3c45c5ca34ba0fa87723ec82826ddbacf081a683be2f5a99196ed65133f845712af76dc711c782178658a8d000a72330440febd1210920525d1cdd7b1eaa37906f8e5ec90a3b41ec9b02e6491fb88f251e04b30a23f8ec0c5b64d8be7896c6628d82b8d7d518b6da6a6fac77336e91ac74b69398f01d7e92943b0aa2e5b4a6ba8185b447512e4c09985cd61731d525b4a69c4bc3089687058df8473b4fe0d9e9247b2542afd563c62a892728d99e41f2321453361f2f4f647c04502ec47ba94e66c20cb2af4fdb7c80718673318c5d4cf46e2895b67d53899f2aeb0205011017c4c7a51a138af0e6cab878c56ae9fb5d98925c4b5394e85179cd77316656b53f6235d35578ea848f42b775cd4e10e08858996db19ef61a393ee7ad918576ac23e4ba994344eee7804e9b780a11b908e07743f9fdb0988dba76cd8262557c79345ccebf2183d42affdad7fa8e6ed0b39350453729b291f7b38944b5493ce5e1f0628a2bb3e40d1005868a19246605fb7f336680ef9816819113721a93038c0f144530533a326a9cd47f511f5e543598b52982f5f052ea6e5d6583bda7debbc00d713e2e409e976776a94d2f546b7a0fb08cd7bcec967490f42390238d45aa7721471e9be2108fe31a3c3cb14938790d231793d6c1a65dcf687b58032329d746cd6dc27bd4b8073f40ec920721fdabe8e99931487f494001c174fcd66bb81b44fade6124695c9f90a56147b8e3279d478268535ae9f119d57ffbd6bae6cf5623069100e5737ff4f25f2924d21d5d5bd2beeb119cffcf18eacb87628003ee3fac1b5d755063a9cc5fcc22d302422250e39f30c351503f2d68fc53afa22ea6bc0d17cad04ef24f29d479794d2424e2a90d901a00031250cdcf8f2fb87ea6f95179fe3adfa62c2189320bfcc37dc1c7dc2be7b699f613f9fabd8113f9fbbe26c6dc52b8f2e47177c93031ab222ccca2ec095341830c78ee0ed81ab638270405d7726c11311b3bf0db69ec37461dc18022e3d9907c260bf7a5e9878a0965804850d527930ee9820eda7ac98da7da8eda4508ed0c7ec450b62e27513a309441b6be3846d44cd0f17a740f677e611abe78958f2c28f2628c8eb2b0e35a45247de93aa2d129bd38d7a480accb2c968534c0d6b711a028e7d1c49c492bc0ca804910a65acdb82ebf24c936820dd5cd24745044d5db3553478451a6f95196fa8ecb8179b1db6863ea1fe01701bd4e371a67041f04a8cd58954d2d57ace1032902503a0db1927d5229100b1d00fa344a14920a2178f1ab418612844d1a75d6ec0584f3952c885756bc49e1c8ad49dd8e22dac6f9bf180cae76e93b4aa9fef10c75bdc214e97b18fe93f1a34be038cff595642490bdfa299d738a301b1d1e06e4929ef5b5d94c7c80ee53355ff2a6f0805ef49406bb2915d823ba699707a542caa8aad91adbfa86f490374bbdebd249d15634a1c37c6faaa1e72a9ea98f07fc3552fb97c5ee2a2419ff1ac887a9ee9a290d29a6b6a3e7bab5d42ba902dbd150f4239022357787a163fb6464202cde5a4dd90d45c4063f7bf67d407676d9802a630c37b52f2f6e424822a051fc61e1d07d8d90e4df4f6e0223623b1780c41263fe5c019f2dfa53043e0656efaf06bf301ab5ca0d83e6cff38cbdf1671510dd29c069c0e86b9dd0e19b46f36ea8eb01c086921fb132d1b905d394fcc45e28921e96c468302826ba446238f37b659492ebaacd3ff4a21d1010ba575df5a5eafd8356417c18042eccf7b9cfe61398df3f124623ce95dce8bbafecda46ed082005d245e429d8b9236f31aaf6ac68ef42811523a8a6f2e5b2327d8ec83d2699ba50168de8fde9acd67888c8ac4f6ecfb77aab893f361473a8c615bcf2f591cc22381e3c4a7dc9a368d425f9c7efc2812cd609144c167a7347a6004d3e2e1e35ac936ad9067b2d90ad7c3bbea1bd600ebf72e472f57ab3eb8f07d146a178602f8c1615a323764887796059a42a4a4887a00f34e2ce20ba31454a88ccca67695a7afa5f3c915309fae92ff0420fc612eabe998e8638d4ff880cc5951d643a01b9dff50da52b336fb55b09aa83e13640702065380dd0e8b0245dab5c05f8f885ac7b7058c124958096ea612c182f979df6b1bbbf6351e0b99a54c78a1df0d62931283472c6999e45fef58d6565f758b518091756d5c203f73784ffc4d4efa6b81e36e58981b0c25bc8e3e0381ecc67b59f1b689ac8b444f2362b04714c0d7810c1e4837954265e6bbc09f87b6053640b7ae1dba26a3c50ac8453ebb6100d756d74df89846559b789da04365c59f2232f69f1a680bc6f5c77fed4a59672ed6a31a9a01648676361cc8af9fe4ffd5c38d223c2781fd6041974cf3b11dc8b17907cb21820a131e0b553f98dd144e4d863963bdc106047e88d0c24d9cf29d4b323d1ab7aab8d3c4f92311254f8d959e1ac90daf4cf9dd0f31120827897b197a0cc44c957c8b868ca83dc29978ad424ce05b67d0ffa9fff9d488d62b50169e4afb07852be7ee6c9b6fb1a03f327cdab830edecd883e81dfa87cf8658cff907f527f1b89a91882a4c47a031327d2947e813414269ce75a0a0e8400cbde0ad6810b22da3fca4d5495bf6470bcf99bc63a24cc8b6ba40e2c1a0d31d901acd7870bc6feee94f7857e608e8d91c18ad18c161d83dc752b42c2f8898d3cea25b1cc05c86a9cf3c0fc7a533acadc5d6040ef84e49e49003d05d4d7be5f0555f621ee0704cc189d32c8fe263c4d8bbbdc610e531a392d9a229fc00f240b0a857e493dc13dbfe22cd4620f510a8607e113cf2df1f8b1dda10dd58fa550a77522e721e656b157f8c47b929be70621ac646f993354f0c9ffc73aaec94c990ca48d95bccfb204f0a8a6f09b65523fba4fe79e447cff5ad29cab77b593176414406430263d8cee3bc04d7a9ea734d338f7d16b8bfa672823ea8d3138da878d3f49cb654bbb13ec0195ee28df5138735f0b45147ebeddad623bd456d9c2a0b8e7d81f5e7b8e4d01f4a99c9965fb082e6947684993e312f0c296783e4815c2086465c10d61782865b05e48167b4fac7c5bd50a51e6c9518d1baa2280970072191e4d5384f81d4bcb8631c80bfd1ea16b5fb5b04ffddc02819900c30219e2046d4c6eaaaf73b1f78ea268d3db0522271223b121d3da8953e8c90797c7b47bef70df7f6a04b7c50d75a1e5f715f662c766175848e5204db8a585d0647ce8e13b357a958cd6439f908f2305792273d5c243926c8409091289f9d7f0b1ba8fdbb4f882f311f0c738d1c622e6dfb50c8d20dff3629bdf67edd55057cedd10b388bc84e72f29a9b52cda9f1d75d044aad37ebe023c87099ebd3f6b471d9c51ab34779643cfe2ca66d42e4343d02f7dd114ee136f60d836af3c0ccdb3902a174cd6068db6585d876e333bf67880ad28dffe103e8a1613c4b3fdcd6f90aa01df06097f3b495961b2cd6e6301f8ea669c454dc090229558f77c5115fa3a9b2a6da722713a60843bb76247504b6d3d5b73ba6fe4d722806b26647589494607b5fb0d361503f5f1fb2684a0f0dcfd12eaff89410ee765e12519bbf8e03399bfb5af0f4e05434e8a723f7b80543321ed1f3260fc090b60324c843797d92a387027d73f39e95631796b34ac44412627e5b9e0f3600c02200dc587ff2bc0aa7ea8b31d0a8bb2df989cc7a742ffecc2ec3ca1384cc157d99830dbad34439d94f6009cabfcfacfbb6e0a898c19ef342d96e9e8078e44d9943fddde59eab2f06648ec1f6c9df9aeae69b115594f1ee328290e0468e81a57cd1adeedc9ede66b74cffc99509138b1630b245a753e822bbd69c07ce670d983ee0448a4ec0b92ba8662fe4fcc357ce5635386266ed0d2e8bf6a005ec5f2eba71df370bffc76be2899496f0137efd2d8965352a7a5e6a3e505eabd2e3053c09b9758a244b08e4e75588c204c8609bc4e5a952219eabf450653ab8df636ae5be64ec4ea7f9cba9d0feb89e81b83f37d45279ff06cbcd7625825c91acf2e91c8584b8952b921cf2ecd2c86566f48677a5d85ca58b958413a0fdf9fa908650fb02bf94eff3b9fc0968803bbef954ce4723d95d62b05dd7726996c64ad5d20d3db0536ea70ff683de52dad34312b8711fab57f3d018d8662a6c1b4bbf8f35150cc28e405520a146f8855da7fc4d1939e357d74109b5e437c4bf64b5bdf7fc4c69cde2cf335e0a2fb6fd49e30854b9e3253b8d9bef487530607742c2f3133607b9b28cf90faf42d66ea58b3a5965bef6e9a26840089c7c1d3bff6bce63d8a86318f4acf1f7e8c793ae174dafe747d329ae5260b672ed3837974b358d077eba776188feb4a7187e6c0233b9db580b2e365721613ccda692d86b990d83c25ba6062ca26cd288669606ed4f08b2c23ab5e4109b501a560493956b77c254e1cca395df7c18aef9dd6e2b5dc38c043ab9a4a83100d0481b9eaa311eaffaf6f73a2e79ca922d723cf1a8749b5aa02df3234729c04e94187003497e653e9c91fd23aec7a3d64bcc74b965fde585efea9bf39d388c5102fe962ba2021efc11f4ccb6ab8a80b142103c60037fa49e1fdd25ca8bc4e5ff095b5f74de909edc397b1506d7fe09c6c2152252297665f572e850c70ce558e614605070084fc847e09d42d9280cca25247c840af377e0aa5d7a6cd84705722de8e0ebe33324873f1946a4893c8276ba5eac1ca30cecf6b01e55cd04e62cfc9ffbb34e8b98ccd48620de251907f4964658a10b5819bc7ccba5566adba003f64cc261ca2d44f0b4aae20c32f80ae6ce1cb9405d9a7c2594e10ed0647f45092a87b9456e80b01a4fe5e4b48549197d4e29055a371519e1efd4a5e0d361b6dc4f782e15b606ce006843e28cd8bcca52e5d94d6e85f9898d9de40972142475562c14d31b9f538cc156110504f0211f264db96c7c739dfb0d472ee11e13558bad9ff2667488770be0ec53ade0f33df7b86331507a8defdace556ee701e8f71bc1580dea09cdb08f76b5c6f27e134f25fec535dbb2d3668da93413fc74e4e23a053fc43c2519176191a0a8d9e88a6ecbf7cb54dffc48629a067b0fcbb89f6a29be4e21fe482fa2932b62e1df21afe893d48c5cf1d77f7ca5cbfe5bf0c051bd739e5a95badfcc10f502e2862ca6a7c5a99193d95fd33e4a38ef416de805b3fff8eda097a8a3e31723493dff3c347d4d7b4a80d58dbea8cf977b0b496e2023260fad1e743a7cc85f440f0517298c6202d7747842f88320163e8bbe18c85b2eabbe27fbbfce4cae0ba3e0456c6795f124d2edd2e8eac761cfe7c9edd4139d4497b9675032792d8793b1967572e18a346ca43480141d8a62cee5b10e1ff413a2517358f5694c857d39a1184d54ec307ddade8283789eb98e2ff36e433406fa4b5e2c2ad298cef092f62177dc70a5ddd52a37ec2642f94c2656210541ea3494816307cf3a0eae5f95025481bee753686ec9d243d8537ad0dccad70551a66ee01b2d622d0cc820db7ecd4767b992b968b2fbbecde7c7c71197dec3c3acafcc6d25d1047c88902d07b648ebcaf38ffddc2748f2119c303771f748b01b91a3cad7f9916debbc7d0a559423a0749a5e77630f62a12a29672ca737fe197738ba72d80cf3147cd902b54fca3940cc501a65056b7ac309f93b37929fbd97402513fdaf1ae6cc7a094d6f60d17cf4826cab1e91fac39bda0a4554532a87716afe66980b9215e9c81abe364de5fb655c4fb373e31213a5d85c12229e7ba6f1642193a8a15d5baef42c2caaceb61e0bdd431f6f79107202b40f0d9694066b732aa79bb7fa2318e8c72ebf98915121e737d34ef10bdf8a15660130198ea48b81626f7236db4001d663b8c328631783e65ace0feb8a6d61fa69d1da292e358a8ba19cb332fae166569a4475499df04306f8726ae5a6a626f7989d602063bd5ef59db96d3b5cba970cc1a9b5158ea37a83c86e971a6cce874db6ac77283122b0c10eca3945f94fe5d45179eaf97f665025f1236ec1f56bafc488ad69434a929dc74976d522a1d2e39f84948dc1957922efc6419beb636809f48e5bc486cbd20e897b111c78904e3737ef24710d024c2bd4650c6d6fe447a15bffba7ef90a7700c8f8eb5d4171ba6c6ec2915891904003e497c9994438fcab8443c6e8d463ccc6695eccba60f4a7123445ced51d8ba9f3a3b80dad5a27a9366fafd864ec190f1ba9c713e6f0723e3777e3a602a3211fe7092170a9c94a2d9b29dcd27324ab1eeeb0f37527ef9a43576719b2ea5f13c7db9c8ad4ce9c738b84540ee561f9265b90352266fa966a8eb88aa2a8ee9c86e7e3fb3cee794bb1d7af1ab303469cc9335b92b688e070f64e0c3dd20f5984efff8aef8c379038a0b47d539bac7b9b04f571865a95756ee3fbfe7ba8ba04d03b592e2071421e2b431e32d81b485e0cc595219bbd98017446b862ce5b59ca784dbcb335417a9ab64ff8fdf77460fa705c4251ab7a2a0c406cbbdada0ea9394f8a6ed94078044c040a06877acd2501b3c710257ff9e50e3a951c92265ce7ddc954ca09ec6bcdff1c829a56b1a9558d958557b7e5db89e3090ae3609afba609274507c9d11427548193920bccb0066ded2ee7fffbfe8ff20d6f0617bbcf2adcf1d0994e33a686e88a69e621feaddbbc00a8147cfb1b207bc932f0c0ad9e65e3531293a0ff2a2961041b6e4482e0d99af826043f614ac0850ffc447babf506608f1785bdb7268d7404aa7e72045dc64dabc3eac39edc22e121076ef96507c24427623f1f1e5fffcda0e1b08dd203e5b0f885155c915c633d8e9828d1e0c6fc9cab908e88f1d7dff60d757e017fa6b823fb76cb8a07fefac78a06edb3f07bd3d191498c3308cd0ac6033ff27773f3bf1001474349bd5b2712dd446b2032140b2086859b5e7ad136603951f683d5f51e492e12266e3af9cf88e7575790fbe1dc08f4132e024e452dc67a64b511985e5801b96528d1ca433d04e6ae9880feefa5dcbc91cb9acfe453dcd57092969fea244fb4890f8065d3f5c8608efe7149ebe3da9a0c1f3168e8bf58cebb1324ba57e16f1a847eb8d259db132922b035ab22f5172d3ed535e6c0334b219312702da0c639972ef6e11c6f5c9d5ba047e705163f531d4e3b4c2c1f90f8e2e7059fa684b9dcfcdc607cbb06c0a0b44e3aa2e2e71f2abbe10ef9bd6df85ca77c85a2c1577023dea72f74aa54189b6d479d4154c15f985249a6ae251973dc39c2d6a310d4188665a49d65119080134014ebb5c63f6df5371923a2785af41430bceec6e3f052491bff67ad7fbe24802eeb164e9844252fdeddc21f4a6a231e7e307aeb3bbc63ad26a7f060c6f8000c03d47e64d18cd24728b85480e5490578edc7d31509c1994e981ffd3c50f3ca52631edab6761d25e332a372c82e4a9cb6fb4ccb4463daa6c8821ad19233598c886a87a08e5053d97863c613525950f45bbe8213b460bcd9d3e0a8a6e01f4a3b9b7c2b4563c63665d4f15576f5c6004174d0246b26c57e165d092df82c835e8b83e54a32c565b542614962829be81ccb4c200e04942eaadbea06e36db1660623af4888c33d17b926d018a1ea35767d111479d9d2d40f7e2e3c0e53586a5d6cac88c771cb1de8a26930795d5026940e5d8ec1d1165418eddabe3e129cbed43a4fe4739b94fe99d0c9adffa8e0a8dbbf85d1aa9fe6b4da48ee69b6f85cc92621bff00d0f3f800f46158b034a5a70d497ed8ff6e6841cee8a35d5f3165c004c7f3d69bdbb5158bb4af4ca3fd458d6158eb33028ac653d21b0a66093937dd54e7c6f24f248ecadc29d5f2c18899fa69c4c615740d674e486626d3722b3cc65525133dc911d47790a8f773806baa29c00b804985867f714c55e706f30ef9e59b8b349ad6df06f1aecc5df520afdbcda851c079ad3780dce594fc28d504560d84a3222caef6dcc7115009ddca7b86b955af6fa8718404628d4991f29ebf2cd4771c6b89561467a77655b99e0842446627bf9af2f44a38fc530379e692d291afb5de7bffc1380b9f106b3153ec3acd962efb15abe9af25a339811badc2b4eddd216d23343b982ebf6b81a5f2459897c03ec4b6ce3f39442a0627dbb7c297efb59a65614011abd5b6615664570ae640abdd2dc22e58a5a0b4e0cbca8d4ca4e74196cc215517741140cf6e547ed6cf88dd5db0ab9075112b6328e6ed681c19a5dab4f8c4caa354a2a031c8d2371a6dc897673704540d616280e273bd4d16e4ed18eacaab5a289625b3a4311b09472663838d7a4912bae11090d536d7fd8798a57614ab5631e3a190abbd6023eb2d845c4c33b2853b711c962b16da866d5a297f93835a6616468ef21f2759b6f85b9c34233527c73c52c37ad81beba3d20400929683db27fc4a19f41ab3c8f73ee2cd54ad789656dd523675d5d4683cbedd06c0da8ddb98f74776fed6b6a406b45f23ecb2a2ed50cbcee717c02f9be6ed256eea151c9722370355ad218d9b8485a85c95b05b4c51cd22e6187a948f4cb3d7fd54504c5dc3916a5674deeb78eecdb7fef50c3a5866bad6329ff67394fa4d2d1e2dc3482a238df19262cb07abae33c5f732ec7aa9da249c27ce4d0b54dfb05d405e7b1085187cd48ca58bb67a47af8476365ed6aec5f0274f1aa1aec17e8344eda159cf8f00d1fe0b812b736006054a95851967da7657362125d8ca1ddab7f45f84ed5fd23ffdbb49ef472c106502c6d0f77f970035110bb0ff539395fee00da753dab05b4eae1b7d72c54aeeed5fc9bb42ba2a276f8d03510714683a9401ac4635697450565e73d5c19ba8248d4bc3e45a046db66ace6378ed3f059fc9595a994b6a2dd326251d73d96b2af634d7a06e43d385a4d3b65f4d4f2ce126cba76a8a96b4d3104b6ff36f18ec17dd58d8d57fce8ca3599c1098e4f0a1ebf0dac96fb5991aacdc59490a2d91937a2a2715484dd6818e36040851e135b16aded8357ce6ce6adfb26d0beeb76ec8ddef45463c6a2b6323af5f74f603c6f8e8870847d0970a3ceb7f75e5bfa7fc77da35172102f774b391eb0b63fed4a7491a534c17bc17ab25670b90782583bdefb6ec6270fa471d35eb6b7ebad4d30d3bab6c91e568b903aa6be4bea163ec7f67ed95eebb9190ffa634e73262c4648e0ac2f1ed34f10cba37c468c03a8795d1d501b1a1907fce99a1cde73aa171504f632b0f2afcb04efa90efa40d800d38b09a29d17fa88a338023ab708d2f8fc0bcdb1eeeeb690bfac5b498353eeb896e45cbbf54f48500ed4c199e9ac8115d54d01e2338b85ba74df85f777e23e0b4490818cfcd0f8f04014cf763f42d8eb2caa66788c9cfc9c74ac7d5ce0cc7fc1a1879e3412c36191c5107b87f1dfc5457c345a6d23541c32cb1f5a5b783a1d0b3b77e7e51b57db711996e725fae5f4fd959d88ae246f3a5c24932ded15cac32f0b3f97a535239010ebd983c04ea933ead5e754fe89ae8afc7961391039ba9870b40eeacea7c3fbe50ad452e8472936e5b3697185191efe38178a324f06d6bcac0b7b31ff09698810bb85682cd82804f2971077a21da8b0215ba9247bc71242b734f6a152ee0eaee60c132beb209899e8ed7a3001e75739afd35795bc2003f686934dbfca760afb623b821183a63b3f315646e45176c48ea77a6c1ef6313f68d68f2936d3010ecc781c59b619e389dc5955d5d5e2ba33ea56f3bda13a0921e0c895b15d48ee7aa73ec383bac3e46a78f735bde943356362963dbb194d3cc4c2790639fcd2dffd3dbad6b4a964e652c22e39d82ea0cc3da3911b972ce0f27ece4749d51c1b012a4d8d044f1099d43f19a4ba85c0c5ae69a1642b4012ff6de074a9b90a28d9b1b2f2fe611a0f7f439c75dd7f47c57ba33ed6a45e977a46870dce3efc316da2e882814f405b4f59840cb4c893402426a0ca4d528fa5387101b65f669cae7ff05d1d178c1bccd220b2a29531d38b003d60129f6199c287da4c3c75d96d61142253b60cdd3c4ed806f510a2f5538790b0471799424586ee060f77b2c0a9983c855904b01fd1eed6d71ac55a68a5537383689f4caded71128d424a46c9209799b5f44d0581de3a7fe1ffce55c8e7f138c47ed0538af002e9f6d447eba076d9d217f4c83ceb1a87698d11bd3d9427b70ce6d8951672b60703dbda332b2378b40a189ed2f0c15010462cdbe232a50a07dcbc4a75ed204cbcab9ca213c84a236a84a1ee0672e1c021af31b9870e3906859d743e8b0c5b0daff2c2d2ca93c91c6c00cb976417cd718a8332d682130c0b376f5a0815b12813d19bb4fb164f036a4a9fc3469b98c21225039549e8f5f093030f8e01298314c6c431ceae3f52e1d8f267c0e278999f5e09b4d4be4a7a2f8541af4fc0811ed8d083c956e505ae8be743682cbd301f95044d7d1356159b1d173ffb355ba18c8b401293a08526402c6d0b7302b695038e99347bd19ecbf0dfc9b8915e4cc6dbeab8fae5fe4043530251d82b6698df7138e537df78a4e4f96468e7f4ec763bd98c006f9225382baba78806902db9fd2396d83d4d2d6e5d958ead0b64f3443533abab56abee1da0e92396202a4ec244be62e6d9d8579eebe9d99ff22252d161f3e033d5e3cc6189c1c200e0bda6ddcacff26db63fbddf5f4efcc882a155dd8186a595fbd9c834e6aef136f48882243fd248095269af4bbb0b78a7d7a9a41c71bb505216cca339e2b40fa4f6369cfde7874e3639193be3381cb059e9ef2324a928daee33c5b7813bb8b439d048bd38b21937bbf9f71bed332939d3fbedf3afec5ac1ebcd70015149d689ff50a5850157e0ffc5559d5a7474cb44796650274da3035e891826968bcc591045e77dbb8e9bc32fda7ba6accf5e1c1f43cf67bfe1a53829fca8a70d7355b13a9168d9a66b6a041da7459729de26d6ad3f0eb670d68b60d624a273b9558aa692d44e41e0d911c606a14b87aa53af8b24e5729bdb3b8f72b611ace222a856fb6b5b3da5dfd41a117eb131381382f10f0dbff6adc2d8762585eef5188cd3948c395a9724c7fa6a34a928826edcecd792d9fc5964fcb58195f7ffa4b6ba9ac47ad8e7d0dd45f16d137bb8410de39ec337114dffe9ae2bf504780100706ff824ab19d8b75303ba0d7dfa06ef4e0f528e1fec4c19bd1370423bd9b88692d7473a1b07f3074a3ee7beb10feb5511a390440d9e92bbef06a9865aa511b37c457231571ec55fdfaddb88f677b75eb02a9e30b10f9f65f55c4b5f749d4b40f6792d6605f5180f908cb012c129c4395ffaf1d11bbb037cbca74df03c62ac73f3c622c5efd6d364999d9f5f3ffc20b9350c6998666ccde81585ad967e07ed8a97c5fee7389033c5b04fb25fa1115f1a626acf229dd17e16de623a60ef0c8cd3c3cc7e1529de2c747c0c02654bbf06d210488e1511259b372701cd89bcb7d03bedf7d13dc0c1432c59d0eab5f116370701b2249dea74e8dbc50507ce8a5a5d297bc590a6ae392beaa837c8e7c5d97906bff3f9c0d799bd38c0a9350b5d98cce334cd9dadef60acbeed72cce0744932461db6d78dab88c61daee90cc9da98232fc10218362e0fe7b5676ea877ed8569a05dce696354812d1d2c04c88a8aa25c960aa4eb3120dece597b4a998efc8eddd689b746f46c51a53e2a98c520acacaa6ec865fcd1e9df7605edd5c813aaea2e3e437499e1e227cf44eba758d09efc4022b8473b611f59323a1c01a352518608d682f830d1d290ec78642c0a47d062a51965676568033cf832f84ceb88f1d69f4dde4f9679294449c4ff09c54e7bad7fc4a4ef6f0ae521f638ec779951543837c060e0b01f8be90e1c7b4882f8480d4b80a32a96e4113b7942022c4c48b9048984b78a9d06af602f6620d968bf149a6c61deb7878bb74deffa1b0986b670bcc25e3d1248f6a9423a53a84fb2bb7e1262161789b250772495174016ff1c7ee91c886c55fb14be0d77f46f80da32064ca62baee169f887978e84f3e8108af869f1c29dd9595cb7aa6d39f7ffe0440ed476928f41ff73c5b9af84b50bc7426f77f66d9fb969e4edcb00755e4bc25edcf6dc9070ae48cb59d4167eee8642fa89766bbc21137ffdffffee8a5fcefa7c82cbfa947fb6aabff2f83123416544cab1726935a5d9474a6aba1699d3d4a5910c64e43a39812a28203a337bc9d3214885dc63cf20ec8cbfe1eebe355ce22268eab1fc4115b05b37277e101a8489578392609da9dde73022b98e627a17eb126d70a880b4f0acc74d10e43e319417f6a8a3a8ee62e0b347b39c2118d35f59a9a3c4e2f8168c1e2cde03242365672051f72357ea47b2794d2af3ef5e9a09b243e4ac5725da9a858617f8f28527ccd2b9d34dddbf322e2dc5956fe9c6c63b0d95b33e458311c7b7cb72df3314d68154cc1e5dc7a933bc919dcc5a85b9599d4160277f41ae5a38b82df63ada73767f230bcdcd3ebc9439d2bded373a64f7528b180b9904ef10e01dfefeb7a4642cf40012024441e18a8296b9b983677319e4d6899f5183ed4d0aad6bb46d4f1674fc0ebdcf9f83701485c1f5c02127f49f375aab172dfd4dce03fbe2d4ef1a0a2932cc9fdf107772fd8a9edc4e869ef014a17990b91d508d5ecdf914d844382dd9a938f7f56a749641b468be875bf8b4cc895a3b248bc41e2ad1a614269216a0a9e9b6eacf19b82212ef1458a2f1f90b4aaf4ec754112b19f13758c1722afd7b392bd747cd7a74663af36feed477451c29281fac3efb979b36185ab3f6b6b0a1034677cd4e8c0cd5ff24a1f9a23e1c28ec6da3b8a9da90ae940b5c5b09cad5864c3d309aa295e8215afb9b100c701a2e68b93a28949f59ff757aaf6c21dafdc0989e2c5afdf6f58ace4544583873f652df147d5f8f89d10a3a9efc43a84bbe407c40eaed6f5f9672924d446fa258d0b3a104b584a90778f799f2534fc6b10c695265b5731ce593fac8f0b1f76fe5b7a6f70debcc8ea4741039222e9fefd88afa6bc3121f2663a6633b7711b6384c35b890fa33cb6dd86f5e4e72f82e4bfed45103064bb4ac1021b5d4686a7c0849c2817af3c220d5e425e24928b1e5244c320fb7ad103f07d30e7fed4bc3165c03b5055851e37b6001f68188a3dcee7d2d545af9d100166ae55dd0e727507aed08761728cba9a33e865964d2396f24aea46100f9afb1ad883d86e3bc61538a1a525798e9b5ecf8ecbe26f6b5ad371f2855a6d78a1a1ddc7d924cc3f78beb859bec98ce05a65a1246d11b7fe5d9569da863a361fbf688c90712d9f990cfea3280476cdeb1d8ec5b47b522ecf1ad8e6c53caa1e0a2c7324629547c186e7c842e80c81f06d34dafafba971ce5b09966cfeef0b2d81a481437923e7e8a1e2976d573b9bdeeb7264148b4daa5d2098620adcda8fb15f6decd711e4eb52192a5e494fb411069f7b2bf21d58d0f6061668b5d3008960e036bba4250ee528ade0d47cf028447665af7a59addbd830a46a8dcff13e30ddb288eb30b9ad2212dec408bf11c8d9833dc5efcb74b55244b36a80e89c0f61d04440cd8707fe2fecc7eb5eee2b131ffbf53e46fced2dabbda1e1bd022ee406e42282f99d67702a4f413cd7ebbf29ae9f500857b2d33d65a13d02f3b001a76945264230b25a331d3aa82e7c548933c97f62cdb4f581891cf2b672263b825d1034b6953b9b9aa9ef20c2ad5fb4554b1554da97b83363136dfdd924374e2a1dbaf1718bc69df4bb1cc026a4a2bad2a6135507f67c1e9da3d5d1ad027a39ce4d8bcd9e28d93e3e55c4746cc78a1cf02e46625088b4ed8b8a3ec379cd031db9120539e0884c328f06820db1a97ed6cdf67d7e357e2efbafed6717518a39971513ad1b545661574b6c5fa71644ca0044fd019f8fb8781b34c6cc1758f1dbbebda07cb52f5e1e47b71b59dbd38bd38e7b53a6b73586d5b7c9381c5d2a43ff3ea88859390f787b8523ed1b4ddac338fc4708ee92dccc934c866ff7007568e01ded140ae2e21171302ef87db9b95811e839964e8c6a75147afe601cd0c01ee070301d885838b8e1db51f3b554ecb1200682d9cf0ce2739bdff671689264818930a3bfdbbb3f6b5467e5dcfcf1c3c47ecde8ec00dcb8636046563761a1b9dbfaf2a72869e6bc383588cdba2a8abba0fdfdf7b37f102c922ac0a594bbcb6dbbab2a2d321dfa83de1bafffdbf6d984acea007036ac3d9ed591d7b965f650e0c0fac4218194660841ebca90c7e36ba6804da5bafc668e4256dff59e567c5e9274b2c04af5ca325b247c21fd6b85ba778806521e61865418b076f82d16fa2c2759726a6fbcb2ed3fa3b09c09b87674005ce1d847b5eb6189d59572425cf66759c1ff9edabc37f46d013393aa070049059e15e92c880eda1bfbeafa7afecb9a4dbb0c67696e95e7a5928fcb0e287f3d2b88918f465cc2d8403a05c1fb2f003f3b0de5fa5a066fe7e8e96d37ccde1827d50bfed06a2a4df60df4edf80724f4bbe2b1e036025793b3a1c150171583af3a84e09f4cf4564836aca670655ae45c429d0305d7aa9d8e3d0ec3a0da5c7ae319dbb7ae5f4ab360588dab08316b49b54638ce57da9cca70982a7ff85c4bca4c5c778a2b6dd8223fb932cdd502ff113625c8535e5e429de06fdfdd689116c3215364322af58a54b5cffb4b2aafa16229fecdb8b2dd2aab6a98e449efe95dcd09fe2f15964cc6ac41f16fbff24b9a832499307bac65d35251a375e007441f2da77e4383900c4a14e7896aab72fd6f8e5e6a0907fdb93c25ae53f130a29e2b468e3df166c1214b54847bfe09934df8ca7f3a91859b4ef2b3e3a3f86d986a6ddd8ca5181a0fde29ec1495f9421acdf2e3111a9585fa5ec4cc614fd6ce333f003f5d9d59b8c4d785b8e8cd0df1ed5f1364cd25d85e2e53321358e549818d0a26107596854bdf11b3bf242b558b20a8afc84179386dea0d52a70e647e966a44e3ee16edb5ebe0279ae280b74604f3b5cee95a279d3a990ae4a970b1a148a67a76892146175adfb166f49a9f2531ef62a63c2dfad93f9e02cd05a89a001692dfabd7101fd80211445d07a55571780e56b7f58905958356d90a21eb5d5563a76a0e5688f3fc4da594d7bdfe09715634c3534e127e21a318a9ef160e7f86034fd67d9feacb2220d380b1eceab3e1d313eccc491a5931d32662adb7ee48156a40b3270968af8f189f3477e6067d51c519e01025224f054644fc89d896bd22c6b6abce2ffc40cbea425f5119018ef7c5ea4a19724c123d8322bb44271f833fb990e471f39772c13815a4273aff7cfdb2c26c4df1606ec92596449074d3871fd08e4464c64e937666503fa66a3b4ee1d392fadedcbe56677b27f25cd2b2b2492e67a3f0a328c8f3d9b45900ce968772abda11e7efd3afb8239cb269124c3011afd54f0e3684f3163cde85d7de529fc67222d08ebcedb844b2ad77df35e711a125f79afbd7eb22c6ddfb6df2cefbae12642cab898f2e52fe4b018342ab00c2db1383c9ca31d4e31e78748b6919717372f987041b49e9fca9d702635df343b472548ac0b684021aa41cdf35b888c34c31a4a82a6635cc1a313919b8f8cc349406f74bad1daa5261cf118d06f369165693c4af6fd3280528ba3d2f7c5260beca8da959215f339f5aff55ba6cd8909c003703140526850d0ac083b60f506055e3946e738e2c05a4bd80a1537a9814ee375734ea9a34e5a7920e97aff949a3968e7eb699057f1e91bcf49454212d7ec7f7bfb76bd5205f2874219ec7fc2de892ab3241ad4697c40b396d0641d04b86faba69a282a63bb0cd23ad28428664fc5aebb40dfade736fa4fea16968fa65b284056058e9c58700550da007dfbfaafd92ad4454a0882fdc058bdef596209253faf35e4c96d3b60cda5749f8d1ab6dc9a13a46eb3a79591b8413818765a167af70ff9deb98542197e47c4a36f54ebdec0c759d4a7197a502f312bb2af2894f7b989ca9caa60ebc63d52f7d8c0277574cd86b8167fb877e9aee190fa09fddcf10faf594fb43994bc3d2e630b9dffcee9f12b11eee3e3fc090eaedaeb9335cd89bf3e1e7c2da2c87d9660e1fadafe5dcd8c12e75385bbfb16d70f3c2215aaed29b29fd5b32c3911d6d9995f3e644478f949c21e01bf17a63442924e2f3e52f7eaa3f95e29263b94dc48cd8a2cb085240f58019fc8945c6ff55e2c4b0ca6f5735c1f2007cca7745a4da2597f40d53ca5f7ab47996a6b6b3789a6638ea935ee21214d9bdf67c2d89da11857223a2fe43ed1188721c19aaa3b4b223719916566f4131911645de112af4c1e432914645044f293bfe3d44ceefe7bef44586dce3ae371cf6da325fbbc529628f53cf3f2df1137cf20d9f238bdf43e715137f3216e47c1ddf20ac3280f9f9cbc392d8970dd578ba6fab2d44831f91f5255f9f5d45d6b2dac87edf230d20f7bf1c210ca4e03f81265a7f4cee6aa6425ca42a828055e3fa1ed5328f7ac33dbe9063a1534e5b344762316c11e171a6b5e0c80e54843595244b71e0ecd9e47316f0e739085e5e637c538a984468e640e7e94d129b879b60fde32b8e944917100324dfbddf49fb9a814ed7c3c4969c2bee9470626b1efc89d37e8d8dd81e17960f05dee160c640ae77ff11eb91ce9f6e7fcfc05ed30d93e1c2ce33553b33c4efb9f4d82b2b2cfa536708fe23daccf75a93cb10fd5ac2b2806a706a7f59b0df893d8f213120cc1c9e760838ae68ffd70074c58e4f66ab13072387aff29d55ca475b08aeb456dce53aee30e8b3571ae0b871360b95207226cf551e7d9da0f89dc54adedb3d70fb5399888c8b84a06cfc00cd87682f18d6f585143fc500ab10e2a231c400fd56598df8f4947782c51ef35b90ee9f440abb5bd275fd747ca7497dd265d31500640f3f4758232b90c2eca9b9a37d45bbf56f1373477eebe9261f16bcf71f0fcd29334c9d6d9bd88b607c11de41a1cd0915b191d4f9c6fed2fdd3bb04d236a532d999e8381a3ca4dc5d7bc0560df5b9048b7be60ca006d7f28f5ec7734f351e5821fbd8a35fe0e59f8be0d69d698141e243d484237f1eb4cc2d3bc9964637ca4f8a61af359cc52217beda15525040e8b7f9fb8fedb7b978614348f00c27c49e034cee46143da0165c295a300638272d3e497a59caffef6648fc68d25dfa72a6ab7af8ed3eec6f569e606cf18a0f50f9e2ab31a2146917ada84e7c0bfd3af7c26d33fbe6fd02ced52e7e339ec10465613572341f348b0bb2153c453a4bd27eb6d720713fd8cf1a1c9e5fdc0523300f2319921b39a04e0113b7a0987e2f965f7a977f8cae61b92786591d83e6f08ad5c417a6f76e0163822ec8dd4af5a573a0777258d0befba816ca09835e16f9b158d301bc211b06418454cc9daa7afbb25a148e51091fc243b602ad0008d5478eb95789fa7691b6b78907b3ba21bd224b2a51a95be20c2f449311d61834419f4d153c847f0b9388e629a3ac45866eccd5326fce680ad10032447284025ecf428f703db8b3e61debfe0b6d7fc062149cf93d9ab5aac243d1bdbcefb915422619e5e82f773a7538bdb8bb1e7684ef2f5035eb32bed3d47826af2cf8cb2f36abd5c1316e9bf405b24089b33d3c59a1016c61d033266594747a09da7fd68244348102d9fde5c66796ee06a9199661bb8854658d85be648581d19cab3f4d878ea28a610f011973cc19ce591cd1852c3e675adfd977bd8991214a5719c47acee2f7a7251501dcc15fd779868e4da06c2257b12ee38a21d1904b2afdbe08e92c6e301420eac2eb02a707a8cbeaf14f7b256e20269a86776316fd3e84b2be54037bf622616340405866e5b31a7c2a229483ff892652495642cc68eed860dd6790dfcd015c5c1711e06fb489175d1608f91bb9f310e558af6bf9de42254e17a3524b96811964056e05334c98691b934523a61b8d6c56a7cd2fb2f34f9573e87ab06d0145ecbc52f92b780cfa4a71e1e7af40bafe3f9dc48487f9c1521e668f09d4aacbebff61c98778ffd5356cdb303b72e73e780bf0aef75266bfb3eea799f7c0700b11ff79341171d1c00fd7421fe66a2e164897d116ffa803ea20b5b7bc2241190d71e07551f126357c8fbb70a3b65d03b0f66c7d7ff62b6e88d7e58543da6844a292db25555fb9d15a03be95088628054194909131607f143afbe2304e759177a327d0e6fe157252d8325c64655a14facf0a0498061f2c8ac4931e02817370977160942dfe713491a98de00e0b7f284f4a0d31a6f7de7f1cf9ce3e47bf8430be4ae062c21efda544b37b6ef345cc98c82a875f9a7273f8998dbe0cacde606c023cbcdd5b35f01f91a7fb143f26092239c87350c28aac87c62e16eba6889542f2071bd162ca435888599605e12f1269257c17e9077d1ed1db7b87f2f546d14176e5958ed1dee851affe4d41a632583ca787240b78d755cf0ba7dd9c30d9b8200d9653b0bb9a4714709a374591f2ffa90658e4f0e9425000202e5c23196b012ae4a5abbf45491e4bb0322b0ea08c1dd688991b3134ebdecc7a492f37bb47585c8bd46586cd29d61efd538202231043606a83a8edf7492ab61092a7af122b36aeeb8481f9a09c85e386b5bcfa1d6d4bfe1d4e1d77b96c312ec0b34e6e86698b5e9fef423fdf62fda13ba7f75245918bcaefcfd7bfb440de918a6b79c5b3626b080bb8edfa8a4d4aa2705c59252337e23cac11572c74c3a45d3a0303bf681e2c5c2ab28d289d75ff1199fef1a10861afa3e4377ae44252a3010ade3879d039c6aa9faaf1dd9cb0416db82d260603c7fae3a73eb4d9e8dda6bdbfbe57ff8a19ae3972f09dac2a3629b16d79f90ceeb16d426d3d135b72f41df740c1450c00fafaa4ca840278c2a8fec7f6412b21ebee7985823677fe76a53b9e4f4afc8c904ddad13e0b2ebd2899330bc2502c33ab4924c7fb2db9dbb15aadd95b128b67d12b4bbf6fbc9822f499886896884b1a11bbe4e581ebf98cb474a7f29ca4b2ee0891baba7ae4f263849246bf5f04d651dae6bb608a7465b4547a486bef7b12cc98221a9cce849c7e39d0e13fa33d6ee6955c10c12f68932f06f7140ecfc525649fc9477c280e4ba4dae27655d2de673e509a58c503d0fcc2efeebedf98a27aee654f34e5f32080f6c825f711b76534626f3d4a88f03cc067a94c8ea3c3ee02718dfaeca15c0fa8a16e82d1a0987e065f4073e1a3aeb176a5ffc6fe4eefc603dc3a8554fcccdc9ba5c0caaae2d6a7bdcc895d2d3e593c47086a0167e1d58933f89f9c942ab121e18789b57f8dc2f98d8326daa5add316c14ff9d9849219e4152c0049e83d2f245947b0d0860a4201c52ea6a1f96efeed8f0243d3f5d27f7fefff2bbbe86cfa95d8f0456aae29c400dfdefc6b1d91d2baefa32a5e400ac9b547351bc0d845226fa5b7af506a80b2f104146d09b9ad3d7bdfe6f42c50c0cea81643f6faaa1df3536c259de3f4bb5f9cce3ead650f3893dd0fa04fdc883036421d960dbeaff9cfa290ac51f7af897f1a8fe54ea03f9a2ee42b2d51b36462889c6b78d3bbf9e3bf3b9c3abc24c5c18c4a34c539cdaac87ab4de5c235fa888845836e03b391a2e9a66fd5b4b714da69c82e62988dc1326aa476d0bdfa74355ca226d2f76341cb96648d0b80b24dc17db8277bcd2dbfd311dfeff459d039c4a6813761d2820df7e0f7de45b0979d0560d7efb6f94126d9b645c30278f21cf05b3a1605bd18fea622256e3cbb180b9977048feb68a8d45f96ca012512b8f258eab400828398a07c000c1f845cc1857667fa5ca19c66d18b8a741b271c9cba44e64e2b317592cb2d596005933a7dddce453cc20eacfe901c5a3305d7b7333c0ef444d4bc38aeefb6ed88b179542c1b6d9eb47232a8310829ecf3bcf37c2e1690b8e9baa572e2dfa2f66408ae34e64faa93e52a9ba065515627cc36c874f0266438c05f526314b7dc2b9c9d21dc450985b7a919ebcc4601eaf3bcfe7b4457efa46c1f6eaf18742603fa6a859fa2aa6b255eeb2b359deca4611b0ac5c6d39c040c53dacce53a40e6fdff3b7a55ddefd5e1f5757e8a0e30f2bd2d4670905e600daaf31fee5d18ae0bf196491001f8f03ac9593e7aee7e0443b6b0221cccd3f7df461587fb2ef23d3be2ebed7b83fc9b4e505540751025fe63b5c05c9c02bbf8521ba38ce6eca2ccdc005b86bf557b73a910226f8d6fccd6967c3042dc42d6296dbc29bc3e91c06ff9fde5e9f2cb60fa860625d71d7e8d97a7e4488f65604aef41a636f8b52c3611f021facced0b80adfe7e23f0bec2cb7d73519d87f05ef312c8e5a44998089d0378bff12fa8e1bbb862eccfe93a4b43cc4d7c01dbd08fa31371743b9ac7b7ba73e05b7f0bc256a935e04d4532ec33c35a5e5b1ef73da9979e8936e3cc7c1625b9e096b7a8de05062055e1821af3a7aa3b6cc9895ec40d54c2ea28b5f73e69ee071b0fbccb77a75276a0a95d85b9f46cc9f13ad2758fecb89b76e8eee74d6dc2aadc8d060a87ee9c1bf8438c15f6b8925629b804a580e847997a406307fbb1ac0bd728be59d082418f35b9faee4fd1dcfe6d85d15301deaed1b72668f1766b59c491ece06be72c1c338ffbb72931a63786666acc5d0e17467690f3ee6b729ea67cfcf787963d51bd7d56ec00f2735152b02febc60d2ded370fa8cc28ee01f3589258726d1ff6a8668ee3c1143236b7640b3918c9be71831db65c2ad587ed57201520ff587c15bafb57e84e1e6fc4d19a9be0d5d5019ce873ce89d39cd7cf8f524a4251a2e04072c88ba58cabac586235ffe4f3b5c8bc0d3c2a7d0311b924d80010e422f108a230464008110e40101d9a1889c52dbff17abaecc8fd2839b9ddf538b75d49b1e1a4a69dd8f9826a2d0354d9f29e56d8c00d398e7a1b2a4910c51faaa9c820f95a7442cb33b1ed96c461b4f517572bed524fe96ac4b3818cfc17a2dfc1344080640689fb60984862225748531c0957134786dacd00da2ab13821cb3da08f45469aa500bdd654df93576e7af7d4e80489f7781f1f43c7eaf98b9cf680ec55c4d93f9c17b8a36f0a5455046b110b60fecc44a291c03f791ee206c4ad518a2815bc3a5c9db0c1ed9808016fff5296d85a250823edec2022d67f9bf52ada441e8008fd14ebd89ed495985dfe8f978b39609bcef7eddd33a87fb78f24dbb0ee4a2313290e2cacb88392b9066f85af67fb08791eb7679c842bcb54c87391a2367dad5af7755a616ed2b26a4bd0c5b79b20f94b24875847d67ca56b605bd9329c841125f92e0d22962f87ccd95c9b5bc86a2f7411d9b283f8eca40459839427f317399736c20e17708334084b62385b91f59f44198c82b1bd9429692b24df1bb98f1912b94603aefb263ea5794ef8b71458047ef47a0911fef8e24a15a54a1cb3edf5d03d40294d53e32668839f31817985c2640d050d5fbf8dfc2b7aa9485c28cd44bcb62c95f77c3fbb44bdc6b87de15098f3b6895c79b535c9c18d04537bdc3b0617948012fd7725943ddef6151debf76d1ebfd993237491432c71b089aacb7b575d03252e2254d3eb633076068a8ac3475ad53cab7f1c4381d8c47320a551dc64c3b0f5a2f18a5fbd0413565c8c733c1ee7273ca82bc774a0913fe1b01ff2d1bc8058b7ba26634ae7ccf9e0bc63adea305c7e294c7fe6f5048dc0fe922a2049dffe9587fb02ca2d0fb7ae7a338f6c99267d59c6fb6b4c3bf8b50d1b0c0ad164f973e627339c0fa5dcb0bd99005bfdc5fdd223cd1c5965b48459e2aa1b25ee9e207a949f0275cc46dd9588512dd988264a552d40d99d86ef59754654a286238658571db464f182027a82f0d19592c50738fa2c25e0434bb2226fd943c227d6a04284515b55c33d751dfc9d620d8d95a1cbf05cc07288c82e414e443cec2d78d466ab528dbbf50507b015e1293538278867c74b28469a601e516280221a63ef275b20e5d339baeba8a281ab662a3f142a98966ed40ee82482105692be64d18d4acfb6b893faaf5b40bf8faf725887053980814f602c5866d094f2a2690e703f8b6abf0a638c90be0c82f621c28053ae05500b0b96bf80b30e90067018a20504c0e3706ead68a2878d5f414afbe90912f1864d143b41e603f3ec8c610645a5720e54485350a02c8064c3e7eb6f50d9495a56d49960bc08b1c1169da41dd6527fc31991516675513c38c79a24c709cd6b9acf9a4bb2f02135e4401397ce049204d1bd359c3fbb6624bfc072a168ac5109ade8ee8a9743755d8bae0c1a6f8120467b2aff3389d00d78359f12107aa1475d97253929041d2d89bc678b5a97088516e611b61cdda3ccc42efac5aa1c79125f1dd2310fe3755f907478969b5366e07b6d7fa466f8bad61f110eca237accd46df4c024d4946d3b25151ac8d9a82f2d5d6408bbe8ba1932726a496ae61a23cda546ce0a57bba509bcb6ea83a8e5336142d94823f4ef8cf231b0e60d0a94499e5e900662fc1b304199d9c47744b4d00456f627448f398d89113dcb4d841d51c90a360df797b08c9d7df4526c697bdfd19e70bb82869d9df8d866f015637cb77eb30c94372c62fb0c199b719f05874eddcb663c99baf27ad67045deec1ebae1295e4c3483e5df71434c0ef05503e537e76d4142565a168d719151bf773e5c5760bcb3046fac510508505bb2f362aaacef4bc5f5e410f3538d01af231e89136cafa5e88403e694a388ab471a49b2a543642a0d5ac039c5e26c5f4f7add1b3c34bbed6285ad150f3261e9f63bbcf29b7ebe1ef5388260f6780af0d46a7fd9116f5e8ec63e9974178764894fe885d58ce02adac9e44d40892e56d602f8189e43ac53e30a0f495cb88dff12bf4636411311a1fdec0de8eb60a1d6f74dcca54212f4efe05326099cf64c89452a4c6fb0a57ccd3ae54c5b4faaf468ae8b4c33f7a2816b7ff32dd27298f42933a527d9a8aa14a99bacf9f5fffca902031c94b57e907fb18eb7048b635a84530837f635b965be356ea2a40eaba4e81838ffd4eaa96f3ffb9ec142508ff01ea2ee4fa2e578171c59556c96f3b6630abb85fa41a748f891ff9cf0ad3968a6604d88c46d959bcd19b3ca79cef91e37cf746bfaa8a86d0193f3b80806ac86e5da6d6aa552c3660fd7c11a17c35b792d6fa2cbc9cef73af25e9b03824b3f4f56acf6fd812ec5b0c773cdd78652a7cfc628ac257dbee3028aea2b722348a69c1305625c65203aa7f875078bc4319075f49f0d512bac173cec0cac4b747e4a45fc7aef84e19773ec49fd3e4e6087680a4280f0fb936319d353cbaf0225039f41024310cd280197d3565056b98e57d6da8167ff15fb3299d92bdb27bb552de641e0932dba71a7ea9a9439f011cf26f4807992a4ab7edb909af0a570a855f838e0869aeae610b3d63c282b0ab6531252bcc20a0163fa91f8dac4f6a735724b486d8e1621eb5fe810be9760b786ed6179df8b9685c5af9e725caa42e509493d1536f7d68319233d9343c4c5d38099b5309468864c926b924d0a0811ec60167f28de787a013f43fe2667aa6d2f0e2955566ad77f194abbf64b8016e2b7d68843702d60e559d48ccd6cd4b7d4156b23d49a46ec9766d0c192c8fb213707d86b67ac7f78fff4b037d31b3779e2897ddbd014ab2eda37f7e94a64e5ebf6e151914be1f798b518d283e46b9163ca0897b5aa0a9b41141505675cd1fee5cb3057154374a40b99dc6cd4a93b8b5807f2ea0bfa1e7e98a4dd45fad17958f28ca942d6debe2ca0894a26840d982e5dd04797780bf1d949d869e080bc6530449f2572870575609f8ba6ce4a3e60ecaedb6b1fe1f3dd9a0fa586ba8bf89529a27847add1b33c408047a06967a97063e312f35dfd71aabfde01301862560d6a8e222ca38bc3ab9db8ef050974e7e8f05fb164dacbb0667706dc5bd2459dbcb7e068e20ecb1b8e76fa431cdd28030d1e7d15f0fc06f835e3d904938cc6fb702ef581be136237773120023a23835e991c57231480ed94e11691ed8538a89574aaf9a230b8ba68fdc40a863bee5f77e912f153b2328d8db4bac5fd48a51456a4d39f4458626ea13cc67b968e40d034c38941fb9b28b5ccae69008f1d7f68a7441be06eb12447fbd5692cd9bb3bb0930ed3bbb0e388761b40650230b21b3f922cb4cf1aa0d4035fa5cfc6fbf1f24fed294471dfa7dbd125b5a0c5108df9f8126b3922fe6167afd81b02f9640c9972ed261651f87453a619784f4a84f39035875ecbf72ad0e4c4f97b4b9f4ec05b7e9ec0da722891dfc7e520f30d328faaec330fea231e1082fcdf9d451997663249fcee0834c181a7e45300e48deb271f6c0c61b458fdcb722bc078aa4de0f6f5f43f8515f6a0e2a9afe9fc1e6885eec10853e0b0ff567d4d8b167bad9e539404bf26b78cb9a134d1a3c76773a2621eda1d18fa574bd6cc536b020035783431543d17728ba6446ec8dc8a7d63898bf1620605e99646873d0f12d3638bbe7063ad9326dc05b3dae4b4785daf0148a6382ee4e85dac9d5e261581078101d414f4627f8ef87de2f6a48d186dc87b52b68330c2d3f8a94a5cd2e6ff32ae727fd4ed0789ffd4051e455f43bc91a748c2ab3baded005bdd1292e8cdedf8b51f76ab31c4388a1837e0360c007afb5c6e1d8b782068f3bc37a26c790cdf38f986508665cc68bb3014fc5f378c0592de4ab1e74f81f41ae349589e0b89b9d74d7092d0eb4e1aed70e74d60177a89373e1d20c1b42d6b6a1d7905e331a5f7c23f77fe63790e18009704b034ff5c31215157ebfeec2d31a4bb3d4c3746505ca7e5d0c1dd1da3b5a3a1afce407e342625dc8e7decdff65c7fd538ec45ec5acb5aa6b49f0b90e15e109d603272df52a8c38c13b6bc4477814963eef463835977e8686d46ea7fafb4d246e6317e9cd5066db84924c7c48597b2c6f476b989ad4266524a9034dca60979b0ab4f9efb5f045a4368bcce9de9edb22263c85e89424b9c399e97f2386b23707dee4f23441598d19d4a680ca25f14e7e2755d79bd22f12311c41b30e93448863e7a59aeb21330454fc5da22eb0b403fe6b53a12f23f00d0e6ceb7419af06f777f442924d4b92829378a6987b87a55c5b2111e422379104c0660f327f1accbf0093b6b0bc2358cc6f4c3fc0221dd6acdd204fe379c34a8d270047fb0ed9e8e08d85daa1de2145e70ab1657f7005f74d1b77c4ee828b987f5f4c3f5b86087a8f2866acf2efee3894db927397307c737253b0a722f26ca0ae3cad804b07654867d9a0f0c2d8ad9c388612196bdf781185f366a3c59736d83f37752f72d89611bd2b5666fb28335bae99962b139649fc1a72bc498463a66d43be890f9821877107ba09e3cb09cee01cc0f1a552fdf7225be874239b93c920c25fbc776dd87b1c26d83922c191023aef56db8435cf24c4ac671649f1cf5fe852eb67f65f8b3c1ca96a17fa4e232b186df6fa62e9fa9f679f1d4414fca0601827820446caa3824996db542c9cdcefb1b1bd2f7ce05c8d7736c1f9c17dea82eb7392ccd544670307e5d6c68bf6888a958821afc2d8d18395fad7167c7b559e04fd76ffced3b0379b3ab00174e5959bcb639961169320a6dc85304e86892066ea31d26c6149dc95f795b8dba43f45ab87f4cee12aa6cd1e159c15d2dde2e0c9c37f7af66f9a5e5e1849bb432b8e25379778377399cd9de35479793d07e0b08c0ecb5d485d60bc85244b87ada456573f61fdf4a8f1168fd25ad1abbb3eca734aab5fd0fad71f93edc4b61ff95702978af2eb98bd8ffe350e080afcd20f443a2aeb730f448820d3c424a1000415feb2ef941964f1b1a635bc0011aacf3bfa980fcd82059dcaad6181bc18e665a15fa5f28c667ab55e730ba3439be6d69b9dfc777faff4aedaf6695c0d0547d43bb4d6688e21d8fedf2c7a1a4334c0ab3feb793cb14037bc8b6b891061fb64737d10e543fba235b63a90d9b9086e8094fe3f2857e0f47caf019ff02eab92ef1b7d9c81076f75ee915ac888cae3a47707665cb9a01697aa2491e92aab03641bc766a155320e6854798df1aa4e2acbe3df65b995da4d4d61630a132cbf2e9d459cab25580eb0202f95c55f0c5cba1d3c7d3edd94d63bf5af4149162cc3dc7a4606ba958f17f582412dfa2d0c4fff48a31b705f662a5d8b1321fb912168d2af68bfcfff142c799a77740176cfb0944eea960b5dde8b5c237dea486b259b78db74f568e80b643116e9f5ecff25bd165b35f9cedf9b7cab3bf2857441a2aa3ce19ff2acc2643781a1513a30a7e4c774f89cc8fb5ee2a8a4c30e7cb503e76473efd35bbf1fca11e42069a136c9e67564a2c6b95521e4ec4a1dca5be40502eceb551aedb70f7bfec72da686b1bb42a05b2156ed221ba7b5f8a5db2fc2fab5fcdcb2e60bd15c1c7cb25cd10bed969d30a84146ad349b5d7496c4370add2714f33709cff31d09bf1424348d673a6db6ce39b03814ec7dd40b5ed919ff3d0cbc2e3fc255fd7dff1315460d12a1c8ce9c53e3b1d95e1ceb0beb1d80584c32c42e207b7d3cc55f35b9c5bd45e965d452a203af20b879a250b08fac451460aee03bc137ababbbbe9f291e9fe0379894da4c573168726782174b4dca257798cffec0f813196be8b294fe84a8889e8c3c54ce77f8c763285e2e61856058a0a92e49a82f49135b538d5b27daf50664a0e11bd3548df0b83e2dc0629beb660612bbbdee4da4242321093dfbc5592d754e64666ec4368d2ea6782ebdccb49db9b239642ed03abc855da0e82a9d128bc872544356179188e19068fda06657b6473baf3eadf0891169a6fa21a79c2568f295c7b2eda3b709f92a2e71a4ca4b62fd46d4fa560a32b2f0da1048ad740f31e2a43c02413b84a7ecb95517c52a2edb002baef3bf2bb27fa26606c0aa44f48081e1cb68481b063113b0c638ccf449e9ec6ef36c03ab6524bb5ac48d67ed362e22af21f0e8b600f0b1fddccd39b8518ce9665d869818658437beed79e937bc4ad8496c2f47b2cd7481ad812eb6d4723afa30654aa56eca307af95d413494fcd28d8a99b00bcc96078b22c1571bb213c1a69046edb923135edc7b091b7ed1811567893b1da9a8b5cb91dd7bed3baac9d446fb175b8381462d4235f4fb697dfdf792c68660436c360e2b9b9a3ff62573bddf6df1b52fc6a873f0a6db213f4797395d660bb98d07e571801f2e0c6730fb3185c4e9115ce961c463a4e118c37f8b920301a1847fbb89c48a5d603e800163d79d3197ee9d099ee704410ebb9b5cb8bf1a9e14c23ed07db5ee3f0ad349e3c4f2bf153dee38a98ad57b327b309be291e8608bb285fe5408ed210330f0a49e8cd0c7ac93d0701fb863b4dc5330d78aea36ebf693fde74a5b841cc7712ca838f7080175c18ea46645f5bdca8558ada75700dbc995af50bc98d8141b0abf66daf8f1fb0ff4c0906aabdfdf80f02bf37369f31b15601aa5b0d2a90a43c94839b084c8a483e5ed8283ef3883980b1643c1b6f94a5892743282a50116f887db7ace65c5efac01dbb6d1a995af63d6dd15d17bccb8fe7b5e483cb90cea62eb70e866cfa12305372ad8ba9e9f1d000344919da60e4cc9ef5c7c7938a7b69fb04999bafbdfa37d3ef1a416a1a3cc7eac02b4d272fb7fe03467a8a8a3e4aacc66d905626d67b7e8f2c5d62de353a3e7b4feed2c0418d8b6b45f92f8583f6c3c59d82f5594eafa1f1800585d962e42877bf35c0e7df98332664bc275f94b7967f0e705822b05dd40085004dfddf9b8742a268a6d72e4dbe2d0bd38eaafe0f921b58d84f8dccd92e16110d0378da430b9c67934fb8c1c39419a454f39fb4c2691b7998d0ef6e0151114afe982d00f46cd5f64b42018f0790dd9a87fd94c821a06f2dbb122cb1c269e242ad9e3c522d5f7e7c22df3ef0f4064550d85cbe14117cd5f14c2d18d6a61dc5af33d9afc015b83f2e842b7334888b1c207bc7f411814d2941ad6b2a2d14fc7ddc9b707f04a82fb6cef7f8260433e1482158136e1de5f3c1f4aefcfb2585ecb7f77b0ee81f230a4675b7a94fecac7f8961300048d5451781c9a312ccd46a576294bcee147ad6a93a2a20e28e98261b71183fb0d22ab5dd5b5a9c31d3c3d96ce726f2464a0704415885ad034a68941213169a1835f8216bf87a3d461198e67a61a47630634d4b4cc03ed1f2180b573f2ab231bfb98ded45b6d074b5f8ffb3694ed65aae173ef853967d93d457117a41008f7c13322403931f7d46a6ddc2994b162548c7494c285b75f39615f6cb37ed3ae9950c5f1c810cc1d4d42b0f4624217b5ecde005c370df8a85a634f46405d70ad4ab2cfc20fee36bc915cf1e3f6b759f3452c27b964dc044a7974283f17444648c259a0681b15215b9ed43f2547fdffbcb5971ee3002cacd33bd5064171bc481314ab12c1dab3280c88a7350087e26988aec937096643a5e56ec9a53565262654c3dc72cae7e51d9aa88c34b109e8d42bbb37a9ab34a66f2c42475689f628a0f88d1502a8e0dd4c126cf90b10aed9f65923de49bbdda2be2986a8fc7489137f69daa7f12a66624e25f057719a700e3efd359768baa805369ec2014423bdc986eb539e3693f648f9dfd5e3b7f0a2777def72cb1c945895ac2cd964727f8e4625df7d199288c4b381216647f5b32e18370032aa1d93a465944ae5dc3924457e5992cb74f797e6a6d6f7d4bb0aa76aeb01c57e9fe90ca283a3461da8834656218a6ed3f9e94b92da59bfb71f5960543f6e24d8e5d8e10686307f9a54d976cc4d434a4cf540beb4e82eb7207312d1be10609deaca75a745c9cf74564dee1aa7906f58f9c97af9a46d690ec1b2fddbd084cb0063cf8f1851dfe96a72c0b2930ecb6b0bc41a798f7a19adbe837c3b65c740cfcdab48c2acc1a0cc9cbcaf08a24cc018fac6d7c52eef3d4ef78110368fda8b959503fb5946f3a81b68a87c76b13a933bd8ed678d1d9cfe78507e58ad407177e31b750d67b6e7fa7cfa856bec1d9c255e584cb5d2fa2734413a292337ab5e1d0ca140884df6ce88cad283385f3cbe7c2065c058af6d4700dc94b86134c4837827b3c2c749e68f2f9eba4e478e1d3a9304b7643a7cdf7f6a99e24a361ca0f33a27e7eb52941836e6269c0da14caa6765898cdf438a80d49be976894f7fa03bc72c4a710c6f475b1a454c28dd464e78a1f0b1098b958ddfadef4c7cd07b640c8980e92d8323c87a3933eed9bd08ad05749b499e76aaeacbd47d0699aee142da07918c710520695335d4ecc39b42cb9ad1c705c8b633fea5d8c09ff7cd5a51eb6c128e19abaa782783604d82a805a7731fd157a8c5f1989b418a80c85120ab48b8353ff5651e2b7e0998fa4557465f33c00bfe03b34575ff5330a5b6e7da3cc7e26967026df4cc181363b501408c27390ef849e6d43aa3cb01f1395c09d74459021c1ddde76a3eea63c563c1ce69c482aeada84906cb08ce1947390d0665c0be36eb93ad2e694bdf73b4e3ea2e14d42a433432186024d45d338c78466676afbef89f3687f598f0dd0bf6bebb8a90527d3fc5fca51a206156cb6b78ae80a32e88307e9cf4fd023dbe7a12c4e9f849513ecf82fac09dd6051d2837d10f3ad44fbfc1d576bfad2f6d3a1be256f241c6bc41d975751586550ff9921509ef5277015462423ee7b4ae90d7b9e4fe3336c5efc05cce9858c9d2813d0d0f5fdcaf9092bb37221ea59514597dc26d3cec892eb56b1bb15f1162f88f2198369ac1fb854aa7dd3f2c3b48e926038efeac7123dd17becf90a5747510390ee3426cf59420c966dc67229fc3b77b1b6d3da072060de31849e5cd9593f79326c2e3ab2571083194be194a5933232af687b51294076684cf810f02e1389dc62add7ee950b1355e5e872f0472e6b8ed321316d7ca29dc832c9dfc4d420cf531f7d8f59796e3f2844fe2380b7743181ec80e2510e98bc5da49ea9f71a7a0d6d5a0c09536e9832431e5a06c7255854389b2101476008a323882917f484be00ea4c4313e97f9933ba0cd38af9b8120e2a95238f742bf914eee0bb9a80aa5442505e51bc91fecec09d09cc4a793dc20fa38c2eee19ea1a3e0b55e5eb2021e6d022e9e6fae0df0bbe62d7f2b3758458fd2cb86d5b4c4d6a3f5bc92c282367e63da142b19b1cedf64ee540af0d776a875ea2045cd10b6db192ec581f3597419fafc18564f6747c64bef4a246f31af28bcfcfe27819d24c253f08cf62009fbf4efb3150383b7e0baef575bd6c66cc7a8a1b84ca407a80354167c322f7699126e7a996ef2f695db6a512686c88dfc15506c57db2294c7219c0b9652a3ab845bf9790902e5f5ff2274f04d5dfc0d9aea02724bdc36d1dc7bc7ce0a6b47b749879b56f71d7ca5e27b5aca3e3dade56ce6deb5555bbcf950fa971b9682ba6e55b942feb1005db42ad4ee3ef2cf068da224960025c3c174aae020f265025248df01ede361491832befa6e4e188a9eae7b7bd3a800e2cc50411f6fc87011b92c65aa356f14ba9364f5092fa35f35b41d0d4beacb1580461c8742cc5f8fc47c45ce0751533fd097746c4dff9cd30c4f8cde83c9905e60ed059c93bea79f87d06a9371eb8970524a3323e4ffb14c782ae8b6636682a9fc6584eb801600342c036628eceb0d2d91ef6f0cf624b91814fad6ce772285a1183fc10710cb630fa93f461c79f810a27e57b678e4c60cc2c932f7d8309776712e1012b6ee5ae1ab645106cde7329282d501c85cae66ca58579077591e9bfd22ed9f073b6f8a9c0dc6e497adf625d15582989e3299b760b492c80d5c22b9f08fb3f6dd17e1f22d50e5dbdb4a3d25115e6e351183706b41df6025256a0190f3f2129f25dbc4a6dff02d9c820e37a09b571437a0d390bfbe8f3b266822c78fa406675b8db0a719c31f1eba1dee94369e8a4fa1e83697cfe3cfe8616643f8dc1d3a4bb6092c09c0f3d12ce41b1126599d3ee624b9399d97e9d3f55c2c54c37fd6ef66da48092e1b1621f620dd03387dac26ac49baf766070c9745f6044e052df2a84cfffb5ca04974a3ad357561f4353d779976b77669174f4d55bf838ab816a1a3f50e414cfe373e6dd8433f1bf0e630c1d0156ffcf71bb70fe8e7e20775f9f57958bc04ee4fa65be3deefe495a39fd869819ed08232a0c4d000ce172e084e0704e2b3305065914123e355c969ac5437c5140b52b2ff0110ce37716a755a8d57087a0db821d2868a6f5b7acf4a468bc22b21b1376519ba6d3c4b00b4e52419446021d4d78fe86408ac4f65af30f488da55aab6ff9670b90935d63b20f39b6172627d3f1e708e14cc928cfac07ae03cf968762f8f07efc4e7afe4631ccdc499e6e2ea506088c8a9d7a87e14d27b3a74975e731f674cc1039b5b3091344a7d266182c0dd027cadd3e8dc2d20e86d857f541b25670caf6bb388d32c5a2f09b72818c0412aef75ef84584ced7e2dc7bc25a3994443ff20928b049f9c7a402cb09aa63d55dbbda335fdc35ceeb22e4d0fa114dba4de5c840de47cfe6f98d0118e9bf0168a4a3aa7814687e205bef4655d35aa3fb7d3090a693f34720c40cbe069a537985a6c011575eafbaab8d19e4e98832481d3ef50f04fdcb2f16701522aff412d208bc1e5b9ec3548bb6d0172882eef5a956aaba6815b1bf769a446b80b6ad79573135fb9409762450b439227ab987225da48e92a676c1235d25331e8cb62d6238e3aeb91fabeca8fa40999f0ffefa4e9c53807efb33269ddfb0ad694b8f1484d1ebcbcb1925300c3e1fcd71e6a695afa75d66ca070feaa7ca357b032abcec221155bb4d1407023a869d4a46742a0ebd4b934b1b99effb2a27d072d5fbae2f1d09bd978bfe0741e165bcc7dc4ccd1a62dda7fc1d3eaf7a70c37e747bbb9638004e647b999e5bc3ad9ef9fbfebb631847971cc5cacd11200e6bf5f6027ca8c2fa3e319b6cabdc5cef0a56ea6ef1ad8a6d39e3b740ed6eadcb1a773467ac58c4c8f3543b96854373ada5239a318876b07e1a7ac5c1cc36bcad036b8c6af51314cb346a794086db40411d33329414a21574260cd46f86b08b18ccea6547c43cf10da41f8818de7af63763f063d860f105c73b1cc2d7faaa4c40b8274e564edcb80c0e20e13719703da201140a3212df25588585f959037a1178c37c75a4eeb5efcb96783235b29098cabaa93bd3ef1b10f0e060a0f0ce866bd7772fe6cdc343c56165b3546b4bc647bc7644472fc183d500ca394c3e910c55c18b4645d818e67de4f67ea7f1c5ad42e06f74819957f977f4604d124fe23063707f7a5be0aafba22f96bd408afe4490355bba03a65a653c6804e3e9518ec639c63fa418dcc157d4b88bac7969ec72385bc2c370e8b7e46646256583341f628e162db99ed11c4f7a379b503dc76c46205657464ce283aedb02308792591f4cf788a0ade4c536ded81be5ce79573667e050af9344c2d142d0ed56a25c3b726cd7cbd9ac5c5c2059e79cc0babdc57cdb9e31a54d2da1adcb73b02c63c74352edbdfe59ebd9cf7d31b4dfd2e9a13caffff25fb9ab8d39ec51b4774d4f1e60b8e8abb598ca5822ac242677adefff116c395fcae430220a0e74dc737c09899f1edc4df3886da7f27f0c95c9f9b17ffe794b07875af1e4517f4e6792a56a8f8ca3ed32238caca389d619d45e3b88f84f09563469138fdbd7cd7a7c8f3f25be1fd7df250d0746899b5cc178c3d2bc3fc92dc034cba0697df507e61dae6b659e604405caf3ba3daad7692280bb78ba3be12af4f3bd9eed36e87df57220f85bfabd7cc0cfaaae68ec2cfcf70f8ef2c5142c3b3f42534912c2304492acdc4c9037a809204766fd7e824aaf17e4800aac165a87fb4c93522c2f49932edfbc15d0f9f8cd6870dc81c39c69b284e9a056f5785289496b4750c7b005b5f474551bac952ff289bfa44a15a40ed89c3ce68fa1b40da789cdc384c6786b5fc8a828fff202b40d07ea700b3e189b2fd38a57e77dc16d2926c6ac4a0dbf1335da341925ddbffe9f2284c90bd08fcb63c7087d201ef6e6c7eb1e17863edfc51b887adecefbe11421975fe847879fe60ea8b83d5b54da37a7e2488f66e049e51d1139e7e25753745be1ad2bcd288c15c1b90fe8ecb496a51beb0a34004fdeb1504a9320163e8d31b3b81b0dc63be382b6a69705e070ba572d48d6f0e40fb81fef7e70629da9e20e1f9b124a6767784e82476add814cd6603b682baf785cacc7f6872fffe143e6382ff120af6ffd152d22ebdee3b177ab3206afc54359fc7f63fdeaa05c1f9fefb1799726d11ee5fec854b5949a01c41f25488d00fe3ce0693977e5bb42ed21e79104223470350bcd67c0aa5699d28677853557ee5b527ab5bc57aab7a98a14793a75d34a8aa806ad8226c8d7c9162490686c7103db80a730c81402f9e48142ee3aaf012f6fc7417aceea0ccfb49480f8480c55fc0a5929ae70d74ef7e1f33974e9354ad58fff68ae5cec233639d30e8d1a804510a8ca16f7229d3171f6173b85cb73718b32dd85e5095f0218101f53f7466e866eaca436853f17303df224a147bda901bcbc4b38fc7daec168fd89ca608bca91573a31f608432f9e0afe04b0e499f23cd96f1f53ced0c72692b088dce0531b39fbb84f9ce30e059d1c75836022bcc17a59d0e93c54695c3035de77952b1f7bdfa104f9d9cf38299aa7e6b226c9e613eeb0bd0794b8afff4ef70671bd5faddced6162a5a85677492d930005cf191df15b80798bfcd0dfb94ee2fb54229289d7a6e4c73acf76cdc32d284a2f3c07e1232042c76e72d49a5b7ae8898e709b5c80f3f82c65f565cf2f1cd35bee7e365554279ec68b3111929e825dbdd5b75fe6978bfe23b46bce9cab14dd5a6ba936613cbe13ec8f189d0b779d51b25e5ef5bcde3f93f9c7ad598868f8ec4cade4e0793b87fd22c841ee095dd5f6b7c777d57c33f700bcd7e2dbd694a61fed521bf40fddfa66d3217399007f286f998ddc2b986fd98461061a30ef0bbf2ad7bff42f9f228fb83919eb938f31f59191c70669739e8efef26b333314a1e97ea2be8fb0726876c6865f74e55222d22f8a29a8256ece4d01a09d7348c35d5653235ab7f3ced6dc6875329712dbe9767e420030bf8383f0c6969c37de4c536cf28774d683d128a4cf7759d52494a8fec896802f99a1509c64e973b8eb9ac570e95d38a75a534cb22e1eaa1dcccf6f8a90897fa12b88590377eafcc7a2e596c6636e7e71f1260b48d0dcb529fb41ba7baeb9261b54dfba513f34e42773af6f46e728ea6b7aaf746b086bdd70e72980b7e561827183e3ff24029e9010f02775321f6ddc5982ca42d92d45f31ac07a49b6fb87d3d33a471eba8102a6a433ca5ba7fdde57d1b29661edde8070d0da1cf8e071fb4d6a378f6fbad09afe328dcfc36b7200a56546b5e2fa15ded5ad682b4922237f0e3eaaefad0be7cdb69e4fb9005196450a9bb3c899d845179903e96db1970d34f8d7dea48747ee784ae9f54292e0d8fdcf205e8ebcd84c0e8b4d0bea3057d0c78b1a4f0cbf5001282bfecec3b024e19ba2f02cf7c668068297bb0d1a1d9b2fe7495b4520775b4f76803307de3d2dbd8f5070d6ef02cb72646fafefbf87d62d9c3e178f27f647ae0fe12abcc21166240106f3b9e528c69dafbc033d1f3caf08d17aa3bab6733bba1c43836d80a6a0e697ff6b5ffc4276ec4ad3816d3cb612999c97b5eba5849d5b276a8d9d64345fe02647f0136df8bf25b1197af1e27eae349ab6e74d1cdd472664c3f4c74cbd4b6d03a3f7e418da746178dd8db86f8a05ddd2f96caf35a25858c160203dae19d891861bfc7ce18776eebdd8552b70b45b10ff8ff88e037e3de71d9762320d91f92c0241d3bb36a22b21bf888ad516ad0962056308d24e8b85ed05db11c54d7233ffabbeb7718dff0e5d74db872a47498918e1205f617c8a7318613ea6624f5830668f7ed112967d671ba3b177fb1d2d2c0ac34547b4f9313c95bdb0bfa6badb096b34ff45a01f4bd52a6dc187b914c09c2f1471956366f64324ae1d8ed4a12d1bb0b426090c11de0f7cdcd4c3204652e3074520bcb40a2c7534b853ff1d80c210ada224175d96b70ebe69c1eb1405b56dd609291ee5780e5f2639470fccf18e0f1737781a5fa5915fc82d94ef7f713ffa1700d5611d35e988c63af578b355d8828b056954885fa9587509c34ff69a0bbfa3319026ef88351410a90e93975c59e1b3aad76c73f43034498c38457cf871b95636488a4750938a05e6da3adc4e22268bc4dded189982d3de1e80715eab76dd12b273e210e8920ed332683b0d303b3c4df479f1c7501fb88f7ef701a8205249d3ebb232226e616f24f534fcd3f6efa3fb25636362705e1620faaa8898e3022d221483bbecda76a34ca27b944508d4e25e4621c58d49d28fe2588810a737895943b75758fe013c76dd8259b0b71c34bc3a109fbe105714923639bca991bd1620e9fa28bfa1fc0bca30c9bcb71ad6a811e8511bdd019a4e46d48fd90c18d7eac5cf0db121fc2b403059aecae7452cd0aab7064773fe714b5e3db410678c8928f57c5d51bda370af7bf8b363eefe6da148ceb2840662266a59c08d66c447c8a7ce379eefe44afd013ba51754f1ae52537af12b7d637e518a1c433251db2e2b3e62dd6a1d091deea79088813b07d9bb9dcf1a11edf5a1b196d277a7d35ccdac6ae1adbda12fb6477c537b5fce099f01f1b836afe2081609826da65c1982ef1fd02a78ab4da7d2b93e4bce7bdc9287fe4d82cf4716904e81691701dcb17517d2cffd968948c4537d29d004e6ec39497f94e9b6472b0cba4d4457a310d8f4f787fce032f2328aeee67f7a80f03d8707221e984bca83c96f4321e9860a253516d07ab793a83f92248a7b70fc7889bcfd5cd0b25e0e0caff444f63fe1ab16ee2e176093cab989343347bd0e4d98135c533a414d03ecd3a97686a08c76ea96b36af278d472dbc78d44e793a6dfad695ea679e5a188b26dbfac890f3f718aa7dbd12c3fc69a5f81041d8d0b3c4d420d3eab31cdc19a48a7feeaffdff1cf2303d4aebff6b161e69b3f84511af7fb686929ebcfc979a269f68c64c5443e60ad01bbfd3771163e1e013ba18e7459fea67102fe2df81c321c576a182b4c14c7ef9a7cec818274c62f3679147b039b74168d54f6966633aecc1e2e5ab18f2289f43033a8ec8dfb7cf21bbbbd4acbc62a577e18489bfb711b00196378d9338a83921a4e019a944d061e170c6a2e6dba340f77cf4f7aceb7b7a9549a8654a7b2a45f963982f856d2e43d110c115796ca745a67bc015783e0c79b170c53e06bfef8252081c7c8c963f583421c7087e0b47908aac56befb97c894181d689a2f48468e85cfb5ad9c232866a4a6dea41f6292dce835d27ccd5b6cf5fd239f61512a81acd24dc96429200158d5b0a30ebc1ee564c6ab8e665eff4fe047f62891ecc650d27f9ff61ecc1432ed927cf75a989915aa1584fd5946253318c75b3ce0a4bad69f2f197a3112c0df3126a8d942643a644c7ad4f94a4a11391b1f93a59bcf64f4832279a9ebed406ea9159b85bfc75caee8a078127daf0f86c68025c6176932beabc84b6a92553fd4435f874d5a6d64cbf9995bc6574680e97534521f147fc97c3b815e03de7ec377b428018e18aae66563fd4f753d581f6974a63932ea7fcd974927cd90a0ee5c6d2e2de015f736a469f59b40307e5a2649b58ce2a0041520502014e7972dfeae94199ec2f8719c231f28d3cc27863dddfdc0271e1222c90c15067c03bd473604d44866244fcae51c28174458119245b04c0bb3728a0edc6aa34b4823bc86533d087484b40f6aff18eca32352d3d4aaa1974d471e81ffa3962b6a03d61f9f130348b31a125449d73fdd36855d19839ac83e29a8d689633a195365f7e4ef68d729665cf53555d5c809a0cdbaaf66ca59f7833f8d8f35033d08812617075171dc885fe9c9f5f27346402b864cb13366183dd3969e696406767e1d0045cc39e3da6240c8c8d3cfd871985659c82c5890ca18ae161ef5d95d7c60000cc0c22966ef672f1d2b364dc5fd7a408adf193e00d94cb2eb3823cf9b01b2b36bc0b20b0fea4313e14a7a80e161fed53c7f07210656da72b074f8702bf36395f456b84ce29d5d2f3787e7eeff979b416f8cc5cebeea914ebc492abad14cbc886e5f43a8a0056280be8bdfb83cf733cb8776c1515c9c8fbf7f35cc669b338f79349da420117bf59102cccf2a4af2ac843d5c71e96bac9d78f8c845215194a5ed7d70d93573c71117fbd633467205ee56abb06d750b9168c9abf91ef8faf529606e6a90ac1447aaa4363f70d807414727656721a039c73d20741802871390f040a19c880a07e0166664e492f1e377168b55b0a4db603b887f66b7c0a36a1d86bc98cc107bfeef6860be08d87c457205f777cc5006848fd4f4ad7756d31eb825d3bc9ef9126ff90b9d209f24de318dfac859f98a840ad621840beca847f050500a0c5890baf0fb2734116f67277fc887c875670e0c7e0dff30a85d8243aa9e4784ef188e8d514c680ab752c142b36b176c76b689ad6ad56b6c8aee5317dcddb67582ed5ef7df6437e6edbf92b589af698bd43fd51b269fe931c4b4b4edf5c0906868f76037c5e37d5d9166dc82664d437ff9087bcce0b966d2562ce3b5a204367466d8028ec37758edae68f547ab6fc7f7842c9d6874bd2898abba309a567403f60cb0b75249786762545faceb8a0799b43c53059378f2bded9604adf243e4bd55a4674d50dff991c886f8327568c4be5f9609a88f8ec5b635e1dbc586b184838b345cb63bdd00da98027bc29f354a508795d586264360dc968d46056ee88e6ff054b5e50902fe0ae6e3216f5bda226e860e72ba640a1c55e141880732bf0adf4850a7be22b053ede0169f4f4525995b339120741f185ab8a4c6ea80566119359245fd3ded6b26e064665bcf1ebdb48156bac9f652430f405f89cbf9ebba19a78dff81fef26fa848aea3b33e37140d38202a874b60b409ed823a9b85d757a09ba8fc832dd5ab41b7174f627e916257e02586f9c29e389a6dff53e952f784ffb86d1da26114c0ebd2259722dfd6940d2ca42e4bf6e2b215d96dd3a33e2f885c71ba34753e4d88402caf9d347eecef98335204817303303485bb0c69ff58727efae2a048335910cd067228fa622bb35944cb8c7792fbafda21e4b3d3d7666b8e471ad08ff4f5b1117fc8c0fa138ef4920066b1edee2e83e67fc86e155127dad8f38fd702ec9a92391bcae96f5f19e5e31ebbc095efa99c250dcd0b052261bcfc11283ef13238f129ce02cd86257e25403a2cb15d56e7402a1f0ff40a3cd982079f6b1aa7f85ab5f10faedb0ef5bfb6cef3a7e2febc51ab24c1ebc113610984075710506f83c41e804247cf49045eb4f71af344208f9d6dd320c819a4a87b1a9aebb717c6ee8b36f0f1c325af8714615777c90cbba06ead35331ca4d5cbba06050832387e051ba782209c5fade1c366dc574c028d7cd7ec85677d46a6e8da612b1371c7acc45ec340c66c0dd74a0c7c62a805a19ae4dd949aed3f859e11d1d9eaf5c4452ec35855cb3621c8150442c6dd22b7c8e3fb7c88495fd959d19f0ec97b1fd5bea1bbdfcf6f67ece345ec3d53ec9e206b1ea989f37003c1fe06ccbf348630b583aa065e63a3ce7b651b22b98a2cb6f869cc6ee301f7b26a4b349d5f4e632ee0b83b4d410c1d31ad3306512731ca9cad89c8ae8cad2e3098fe31fda061f71350beab79eefb0b5c5a003ea39dbdc28ff167cc50c03d2bf98a2f61e333ae7b2ce0b64581ffe2e9f91f5ccfaca3600a298bad56a98b10705ebbf869bb67933a9a160c425af8e67cc9bd7dd92ec3e37ee5bb92b3e3088eb5b32b2e8d9db236f6f36544dd077089a128cdf9a06db50a802b8307f2fedbf64e90b454ceda64403db7c6ed7f00bc624d50ee8f5e9634750653b6f81f5df8b1ee53abecacb70a357a977b07ee60a21848e2e3fdb3c19f1fd5b634e5d88c6704f5bbfc334b83c50402895ca819ae61bd4f10b111ba70837b18a480ce5cf2d0d1e9e462000cf0e8abae565276b13a6ef9f043868f6dd188721612835fe37d4e22ced22a953106145a314390879fbfd4e6bd8d0ce4d81ed36ef7382408e939cfbbb90f5cf7217a43e348e3c7aa288e92dc2b79876a2df61b6647b82183c3e77bf397a8f62d78cb9236837f8a2a1ba9e82dfb2eb9431cd8d6a83e999175323bbb14c92e6205edd32f78be4800e5a0e7fa75e7a2671cd4353ff4057453b3bad6ef3be4aefe443480cdb895311d95a8ac50064fc342793171249292b01949e634789068a133e692436f7b900471fb1ecb1251baf14ee1611008eff31064d385b12bdfcfd952115290327698eadd4a4d673d82c2c7e2db24b114f600f2a1cc272f6b369233f9cf3f8ad37fb098dcedbc7b971eb0b3a1e3c7b669db216186a5336d0b339dfb4b532cbfeec3ca87498a0bf30e63d7a9fb7cab99f74ccc6bb53dbc234b8fd88095442e1d6ad8f0a62b72e1e99745df76efaf8d58af1fec79f48e9797ac68a46797f08041bb4d1a8ee3ab82bbd233730390e8912dc43fbe7d5ed43a38ec12510cd48e53371fd1fcc383250803b836399d951bddb0b125ccf6a1616a61a740f96ab1d875422f4bba046a9d209a64f171b34fdb87d2a5f258ca01d6cf0f3b02b819edc734f46a75219abb22b04bb5b6e6c7a1460dca4862c96f4f206ccfe2cd76fa81027b49836f16f6de2ff282a4d92751b8cbc1e98cb91113563cef18b0a333510c5dd051a15930a0b004a2be52eb64549c5a73462047b50ea34befd064d117a47d2efe1b59f31569d1823d6af8519972e79e80f1dbbae1df69b7b441fa17ade0c59d1028560508ee02237bff3519d097549ec9045d552f59cbadf6ad57ff959da97e612b9e57b6ddcfd0656b3dff76220bbd3a51dc9bf72ba5412a2b187c24cc909eb33e8f296ddb4950465ff51a86d0da642b32d6e4dd19b3dd9b77c77f16fd963037757f84f880334179b8f64f8426f6dd75a6880b44429b26e3dea865bbd0d47f5ef27a80fb132995aa105ae0fe5204e4e6e93d88dfa37652474fc17310dcd9caebb185b38ef52c7167d972ffe6eb08baa074cf387aa0c0f0690890fad953a40e5f776315b87da9879bb3b905a6f50da18bfefd49945aca988917ae6e026026ef91e935946ec90ed0510c7a0af465dee76347e5fbac795b1a66477c671ed4d4c0bc15a3e9440e8b12c985bf18f853eb4981d23f1260db8e0a40483ecbfcd203b3a8cedca4ea928498840c418573db977f7be4c30d910cdd91a57e3ba116247115a7cf40bf47773243b44f29aa7bd2088a66a004fc33a7fdcdd54f9e3568697a697486d1c0b618264563128b9d7ca169b0587a9cb4e182edda94e331c42fc9568567e8621ee071e5e5ed6c432cb13f924d2db2406591527287e5022b8716c2bac0d767311777c19b01a53bb90ca85d13d15223b78b1dcfece8a02ce6a37ffdb76852fc1174d6173c20a4c14f61abbb932e0dd1e0632c6e14d628429cdd63c904144bc11a527adec3a1c7dcc56dc2b3d895e1e9770b5a8b6352df7b715406e4caf55411734d5da7d13f4c58136271507fb3a2822d91c2278611d0c33ca1e0be8bb0756323a0140990ac14a12caa1000fa62432c58c408b15c41ca68118fc09df7d16120182a9492fd7192ab855d611a846791b449df16be4c290a577ad6c6a94ca0d443ea89507965d2512f6ac5f35854c0b5a07e54de7e36bb04ab0c38e5e866e2c4bfa9b8f1edb42065074ec46f6e3a7a886b8a72b56e79fa83c76c010acf5d7f50ee1acad95e3a4c977d460f6d26cc4d72f5a909ecd5fbf3bcde3e22c784828135380b507b0487311d371dc4fbd75f59eb18aaa4c3d65ebdca1159cce9a507cbdc25af10fc5c40303aafd354a401ae71ca0dd6d9a581b71ce6cfdf01685077337f80fdec5d5fbfed825a9c82c258acc448139caa056b40a33a9a15cd82f5efd5d9049380109f08debc43b1aa2873ac7d585120d20ab7d567b2713342afb7a492648dffcc049ff82a6882764f9ffbedffc3c3c7ddcfa195e79fc20da63462e42ef1fb822a8d6d480d33699460e8ed20974001e6db8af81ab22af7bf83bd261731dfd6237c22563f824595867543a3f8c8bb8233364323be292565e4912cd6bc73b50cdbcd0988f9e24520d5684bd54cf7ca440ad57f21adccbc65a89122978746e1aff2b2c5d8a07f323ecbf2d5614ee2f8aca46b2d20f93fe0b14b66e972022d2ce00a0a315c33cec1979aafc7d4cc69fe0c2733d9abcdc051b2c5e666b55587a333318d7c9159fdea04c7cf260696ca25ac21d11a3c6cb151beededbdc2295a6c0a1337f7b6d4d302bc906c664bc8ff612227bdc806a4d3d5ee0e5c738cbffe1a4245a556a0e3beb5eb74d8f32347d4f70ec211b0264dde4b1efca2bde05c729a7e620675b178ef1908c98a5ce195d0512d667aad8128133086be51c7f8f52780c696ddbfdccdf26c9e4dc235d28d0891f61ab3a35b92744231914a883a553ff02b4897ea6713286184dac7dca77d91865fffce3fbccfc22d7ff112d6af3750c7f47e096aef3caab53152efe72fd496fe322118814a59bdc180e4734185e5e94ed78cdde6b87a6edf015f35929ffc80e53656d3fcd39c927712f2df2cd525bed9d4f9b19ef7f14e422210fe23b99064f1d5ed8d7e0ece992b09ecf93b0e3712d22b309720cf72a740cbe39f7e00edbacada8074d4749a8713060cd2fab0daaca30be41f2bbbc225758b435603c448454dbaab420ad770353a50340f07bca0f6a085a201c3c39664063d315cb898f0e5c6e53d597320516b667fdb7a334769d7ddf7fb0420138d69430a3c4e256f748d9f1266c510db2ea4cbc66762abd310c396ec7bf6ddaaeb07da5e467d06ad1c500ae913bc7e7fe17f92a98cd96ab7efd91288a4ab0dfa630baf3d4df200bc1f1936095f793ea8623499ab61e3a8eccc747385d2efa56c6e3fa0328bda72021bbc9f84e2e813b0a250952ee53ed2d234fb64d0af6cef04d408caae73a72b767ea668754e8a92fc479bcd0a9222b452aa793f8873d6691210307f6077232288ddb6d0fc6ac4af73de3343e83b44f7ba2137e4412f907bf90b56dd81e313592cfe58b9492bb4d1666d4f0fdb03aa3bbb28cd20e774c49044dd99f7904f4799c7a26d35d825a8f80c7bbc64889a2087b66f1abd69e0ef94512e69a3d0457137c781b52215bfb6e33c3558c3b7ab14a4d565cf05594372192b981f9df56809f6629615b15db7c24fafcb1d1410b3019a2eb6fc259872b927c2c82991054949f3f9d9c34faa55c6ae4ceebeb705bbfb1535b15d40a1fd4782da9c0176235343c3a5aaf531eff82f3225f7bcb0c670815825ceec3b8f823b35b9de07e2c44f240e021850fc0ff9b5d8ca1d5ff1e682213b148a96de47228485c3ac56192d21630cf9e1c7b3a3611e17d611258061a06555f67683aa6cf1de7432a1446ba37850e58b99a31db740443e2440743184466bc51765cd98da0fdc0f413ec9212fcc49d6d46d54298c213af90036db1fc24f0db6dc0e97318f36f9f4370a278520faddf39bf09af08f92730432551471a4415d7c317e935da3da059f65dcd3af47aa1474ddc36c6bf3fe68dc0cce6b18bca045e8811419c307caafdf2c5ac89a703acc6cf213debc802c2a71539c62f00f94d4788f3c4ff554f9467ad700e46ad64c79b35ae89e249c415ac557d5903f24af39475a22abbbd3bc55e7a7bc5d481360da60c537b3fc1091e11f1b037606126488afeb6cd5fbc0355a389b77ff041e8611d8ed01e9374ae8332ea929e81c36d98314588432e186c9f8d9c5c3224b9f0dee44e0918be9706afdf15adb2d015e95250dae15102be2faa3bdff66faec9f8b011705333a5064900c435ea396cf1238b505d22fe5ef104be3091d6bb7bf9ad5287c9b8cd578cedf186c51e745bec787699a16fe426c931c422881d169042664c86418710bee02190466ed38fabb219b995d641de7f08244e74ee0458b1a66a98b695ea04ec19faf25e2d6c0782c1a3c714282dbdf8db599ab9851f0f6cc1fe1b5164fcd67615c6c412ce2f8e71e93699e6e099a999beb741d658e723ec717d6c5eaa29daa5a7fd4da383ea4670c23db13a5cfd66adabbf294641689885d62acc7eb720e48cd2959b8209e08b6c2a2f7e541ee358826a70c3ffbd2fd0d9375418d44a868a60891f382fa28cc1fa2205f5d9da7d93450a76965525aa663e62bc28a9ed99e2021ef275750f856fc80017d1b8a853e8b20201826a186978dff50db53c6f2955cf876118b65a6a6e4542c5ef6ef8eebebfae3f0ca5f363e357cfe0907ec7c886410ea93c3f01ebab92cfde358ebfa5b2857978bb8f9949020fe7bcc8a84670090bc91f1b1ffcd0376c8f53ac01ff5bba147edd9f5947ab154bd3381d988aa588ab43b0f69e3b68e491ac835000629bd82cdec6d5a3aef66d486bc6de82396a9f6b42015324e3f77f7476d9630b6ce24cf5fa12c5ce1db8570e7b75edae8f02153fc30dfe2559c7276406d55335be57260255ca14991429a482be7c2084cc6d80db64b48182658aa3a99dbfc792c82a8ec551371c8ae234b727a129fe87b638de7bc11a51e72754cc7ff84590f953b3eb1097009eb4ae2146ea88f76bbb1d0426b447c862948067b8cf5f17491d7a5b8a26e6ef8210fdf4a335dd86e4e0859bb8caea6fef39437b8c52f957a258ff9f9018b2a1cf7da56e1c6c028684bb1d8b1270234d7b4988525003daf7304eb8664490ce46036705d4ea3539d9025ccc0bb0c26d54587f352a9e9da48bb223c73b76521e6b61d686f7c2e7ff1b00882d9670039162f8cb8e9c6d1ac7a7c128e8f0d3df44e0daf56bfe9e5c248efebf5e637fe78f748870ca48704a0c3dd2152b2cdfa27bd19d0e1772208c70e2fc42e64266260f2ac94d00f4f757e50a2701b4a917207b47f20349a82114a19131f05efc6742f9704ccaaf51ee59e11dee2aae0cb48a0b9715280ed741b38277a6525f02737f7b21d4da4a57e762aff4e911c6d2bc62d66a17e190b5416afcdef94b092fd8483f5c08bab2beb37eae4032f8cf35f22462459e3d929a8194df605ae7948443e134d1efe557ac95c6d3cffdfce8d19f2ba8e100895780558556279af847f4222bbf1ca572d90ea9d5d404fb9e1684b6ee75c6ddf533ba0747e0342c44a172d949f77fe923c9e38f7ac3134355da23e4e492e0eca41969cc102b6dff0a249ecb8b24ad7f93f3beae473454973729dca7e66670704b61635855227087a3db603f6b20c1cfd7a0e97e112b6d18304cedf25aada07121fe3c5b46e3da139ae9b469d01a0216a3e2274fb4f37604b106ab212a44c220f72c81f0168b0300ad33ec6123f414e3162db30c295f9e91abda1b491742a3841206d4c5aebf8db8d2b90e219f2a6dbc96efe0ce0968847ba7e3368e37afa829d73ba8d60ab4d6ce072b0afaffb13637cdb00eb7f17c44f50732ae6ad191d45646897482efcc51fa1763c262067c0139e5696fcd3ac3bceb8776cffa2efbf9c433f2c967890bf47771cc98887a24af64acc1bc617c9fa4939bcc64229350aba77ad115d440c106dc14596866948b8f39e917c77d8c3cb42ad00a21752778954f99bfa7da1d807a86ebbf57fd1eaf6e79dfcfe85137b37eafab0d4ab4f4ab5eab0bfa553aacefe1506010ab30203b3b2da858a3e26a0d8702e7ebf76b361255b9eab4eca144d42405cd46c6712db488bb4c99e47f8968eda2ee8ae59b120e8cd13261485ce6d10709d66712c512873a4c9e8cc98ca6df59e1163cb5aaa648dd5fafe86aeaa78c177617f387fe6145ba0f76213c25b37c107972817643f12b720b6e8f2a28dd222cf07a382489f2ca4be5811ad93e56a00824c50003d73aefefe9ce97ba70d708b96be1b243cd88a3e490f79d53c6c6396961995d3dfc42a9496df4a14c3fac1a59f0bc834ec4c2aec3ef951cceb1cf7ff551c02476f9e345e9035fe8661776ba5d301e721c82b4a1297770ff593af0b2a1d7ccf258210904e500768c2b48f16d16d3d4df3c30f65f8f93585322553daa13a98512e1cb1e7c60265af1bcf46d07f36348a2569e41fed7f213e9da9017f00939c4afe880b7ff1f93480063f6485dadd19e4b5d0b0bbbc0c1286029435d1814ccf606d78d5dd8c5ed50c6195171b60eb4ead9e607f2ccff64fa3cd8770624d3667e5adf9df626cda473da547c6966be026409676b38cd26c7f28b2a5679727680a5d3f1aabf4035f4d010057f15dd97afe3def0493bda38bff81faf3caaee281c411f75b10987dcfcd74c4faa4f0a3a3435d54b823076033cc9866e6265f0c1fdd8c34763b3a7fdb5cf2aef369dd0376c8fe627524cd6b328e67a1cb1dd04389751dc3fa61eebedf3f27b691c973a67f875f9ee8a0e53b62a24dfbf29a71a992e22ed0c661f6476362020146a1b007e24357fbd6011494bfa3a61b06c574083a3dfd943b076c6001d474c89f4cb124fab1e4fa78f1cd9852bcdd8bdeb58737cdffed2dcd8eeb665b0cd1a78d7905bf7ed49ccce7c6a7e9a6cc738d5f4120d85ec4617b79ee650dbb20cf7a2f8d29cbb09394051e7e80d89372c4c979ecea130c47b7aa5de33426cfc33a91f59f62fefe49d32767d0a88b538119ff392f1497f1ae2c5d709781cf9da19708e017c978a71a1be7a529afcb25788195cb64077f4545173f41ae0575ec7bc163ee160acbc84f64ff23d9c11e0e9ef3782202697b314cfd467e3255eebe2fc8a8099ace952964a67b5a0c05a282a38d97b3f7a250808e8e47ba91dfaa038ec4df2461436c05637171fde96d37245e98e056eaf518a2e891c71d5598b7657414513f0e4472917087d5233205f79250f86632beb9faf5663741247cb55f8f21c742d155cd9bfc43dcdcc5a95bafc8070dc4578937e47f47fb5b9bfdc24a2d3fda453c9e6dceb5df88b9646cf598323a695f40b9b1c6699d1503af0b8bab0a51241363b926158ed0f351288570f0f61c2bfc95690ae4bf4d4c2acdbea793e948592d9dda9464dd15fc4ee6be96e561505a38e246e0a01a90c2421c0f7d51d52c68a486a07436cb441a339510c35ab3b9d5eb4e5d596e1343bb58b4693aa5f227009a5ee6fca879bca6d510fbba0f93de5ab3ea46bd8f7afc758def8ffd949c3fd85d10ae659246320add64b056a4968f592e7d5416bcbd62570e9c396acceeefc57e9334f23baa0edb74b6214a1cb8cd6848e5b8931339845b21d4acbdfa1cbd32ad0f0c3859adf69a999d74b238a4f2740d35fedeb846fbd0bf52c8a085ecfb94c02a4e4542e6aeefb36ca6fe57b4f456ede86825e63efc0cc32484d0af87b8d1f4d85416634125146cdb61165f84d8db67596df5288ae74cb10ca47346697ed009bbfe3ad8dc87fa9209bb50d094f7bbd80d27427d3043460e7e23749225fede5d21590be5d37cc60bb0b7e047607bfbcece47873987297c55f02947a3564c60ea73fe239a735a13999dda305a360b0b46709b2879aaff5b5caeed34f8ee27eac2c2d5954435ee4982e9ff65892fa92aff599e065af1ee0192a26a023b98de9be47f89b8d4c9057683ceb67a2a89bbc894ba4f5bcac97f61c779cc802f50a05878aa5f4960ccc5023c4da557aa1a0def7bbef1a112fffa4be1b06f4b52ad04e872d71480ed779dd0ebdcd1657632793922bb38deb4184b17e7da86dc9c1b53ff4538665cdbb07d00b3e362fbb7a3c14fca4217c06f6a93ac5d1b7cdc7187e94ae2529bc5f35e2f3a46edce8f23243612a3047098e338a1e0f4b970a4802f3f3089aaad2f1df58822869ab793457037ba38a887418223dfc9fc17b4fc6d0501c7eb74a2edc6f3a34348dce8c7e24f3d71730a9f465b5b255681f8b5a9a33974409d34eb86b9a7a478fb4441fa30d3845785fa6821b62c60115192e26aaf08c6ac034c08d0aac950168562581f6ab984f28862c164200ac6e0d77d947bd7af5f1a6f7d2aa6474b5294a62d3adbd011d73ba4da13c09e5d3c23f324e026d963ac25d6f7675bf210877db954ce3a4ecd9563d8917bcf93df27bdfad7bf4ae70c3f90c7e95f8eaab2a6d3f27a579d64947b2677bf2c60b4d555ec77758bcd35bffc82d98d81bb7b3e898dccc7c1ad251a70261238a6f9841e9f99d1eee560fd7bf5fac34bf24921d1b5a892240a9a2aa81970ce5b793a2350f8bc2ffd62a05153c66970d1112f524d56eaaf5e14b03caa4eed001101e3969eed9eb00e35467f31ca38753deb459cd4d5337336c2d6fe833d2315702ee17a0bc8defd73ac1d0f09da909ccda865aab43a8e5792c57e5abb687fb1d32817d244023277f40a3be0c9e771af85a8c0d22e0be18fbec0ab855382bfc08f860670ea7487fcb3584d5ac7f579585fabe2e79191686cbf26a2fbe68c0d4d51d5e4d19fa1858b933ba37e627ddb98a9e9024116614f8aab8ae4d589715be4d6266a2136bff9233770e18701256333b6acdbaa4341892d03557e89706cf5b57b22ffee5e6c85e01d8b0f57cf0b671a4ebf4a6b252e0fca989d0b5e8c0f8648138240adad89e4b62c92cf1286a2b0c31a5c8424b55fdeec0c1d7ea2236f9a3b1a5f1858c6a9a358cbf8278b52051645019a8fbc8cac6ab8152b5a98d181294279816e7eb15a945e0049449900b6272f3c52ea3820b2b8ab8ce13ff261240919eab98720e15cb12c57f15e8671fca351a4becfa0fc55cffa98304e3025fd4a05bae12e2b29ca632c604a7f2f8ae95a198d0c930d74c7f9e7e7ea5730c8e7cfc3e2ff67cd5e2cacd21433112dee2c7ccdefa4d313640e38def90daa5f66e3c9677a2d89c85278f5d64a1902f77d7a158dc301f2b42685418360428fd8615fbe685b192187c8b3bf24c617633ddb9f3f2a49fb572d20a1fd471c7064e2ac6bd277165f924bc9f9108bfa28a0e1ab4c2b151b8d42ab9a5c158e567641fe3703a09e04d9c555f15410df266e74e2ca6826910772447279260614fe9cab44eea21853f44e6a22617ff0488c72d06a778a432facd029d2ca2c435bd8ea827b4bbad3de7e2cc4e523d1d21ebf47b7e46d1c0156dde0d81c1e8e9265fe316c87c39fe989aba2a6114b285caa6c3891778552d21bfff2a68d2b7a3867a730e41e1099db84aa1d37ae8ef5e63d670d9f586c7c7cd034c107a575c03c570d19975d8b9507f1536b4d48d69ee1245346252504c9a74f7a8c7e3349a776381768e62cfb7a1ba7fc3cd50dce19858e77d717f232adecdbf0b63242064f455472e8f2e13bf0ba75472baff1c9e65c944bbe11df2b4f136dc0336ac36e6041f95b945faedf0a00599795a091622c5d0f3cd30843e5c278d4ebe03739b3585ab858cd557d923b227675f144d07b1ec5fc4a25021afba84978c99647860dd8e76950775084bb3e0ecb88d0cd647cf645a5ae90abb9b28bc093707b445e8868cd84c2f0d540e5be3e566c51e2574c689baf9691753800af31ba9cd81dcb8b3914090b0a97ab23bc3ed3c205a4d974ab12f07f297efb4c139ff35dea9bd485a7c8d7c3a84b28b394209080aa9d8a0d04029580577710bf902823802b83243c03a8f330e5b24ced0411924ab18f4ac9c1a0244255d85959de1b1467cce3fbfac43f35281fe0a791c5d92b8caa4f8cbd3d478f357d7d764ed4c0e2ad736a8b8bc9ec1044ed71a1937b5ea18eb891ef4f86a9b386e9639a542a51e0129128012711a506905b353bd1cdc328de1ff865fae3c38ccf1f76150170ad617741d08d0794145e0f30e24ca17bb2fd477d9ef0aeb7d1f4f684269e7500296cda47e7267774af6ffcf96d3b399a26cac38fef86779b1197a6d87872254f97a7450ed53789d4eb018bf3cdda87cd3b6374de5b4c3341893cb17a76c92a93adef6a833dd2b5c84ca85ca3113996d6d36a6f7fcb7bb4bb9368a5ca47bcffa5d6c4560286ac25a4538bfe560906e439b48d5a67875a4c461315c81b5967db46388e6f3d0e73a5baa16a44c5c06040dedadcba69f5e7e2aacab546c9762ba574253366720215e9c73a4e94b5ff5fdd1ba79c9d0174a77fc4604e8f021bf6b1f5f75f67954110a53d41ad352c42e2cb020ec3190fef42227caa1d6f617372220eaa37e2446518d1ffaeb675951d694ca6c2e27994205fde50e3ab7389db3733a5a5baa35a05b349665b1436df2195c89f8e9cddb02db1bd69ee0ad4a78fbc347c2efc3775eadfa63401ebe1fb2156ba594b7c71d89fe37b5094fad0673b671b39993dc327329368e6861ecc07266d75606a852ccbcf96ab421e5ff816d694ddaf8be90aab55b9c8dc1d62d75064969f72bf02f05236559d22d1d6b667f33d4adc1d276cd23bafbff8317bc54dda102514ecdd05363f615b9c63f5119317033421e94faab150f49f9be7b8dbff3c98722d2470f32cb0f3e8af0ee1f143d429d1ac01c5ab7374153f2fc5ee2b0076aa0db14d2af76f35533ba24eba4d99d6b7b42a5617eca12f1d23b9820625b2b84130db2eb107e6a1df37969b3ea0feda1e8628011ce93ad6f109f502ccb8f2e1ad19b22586e11844bf61a070083c54a44412b0c1d9baca7fdfe43897084f0d4db0520beb1f9cebf9ff1d716e588ad4e7dc4c3067c379ba6e4c8df3bc9a63c83a5d206a5a32e41513d13f732d5136a6020e54a27f53d324deb0f939f571a5718180d383d09b4ff105f6f7246bb7e06faa2d95bf734b9b85feb8d4305f670d3d5784b862ceb71c0bbb25c0873fb18457bde73b21463c6537d2d6f4445eb12dec4930113a1c7f9eab1b459582bc16d148750f47845a761693be20237b18c579a8d1c1f8cf67f3b800db7d80c15647c6988edcb8d753869e731ade6276810b8231ba7a0927669b8facc1263c9deac7d6266112e322ae40ecc47ae989f168893ecb8cec1c7f4d972a2d2380e4679fbbd7a227a5564c40aca589f2598fec8f251129b3d81ec78f5848add221a3130d76331c61eba572681c598319e935b1ce3874d49d40bef69777c44e3f037c8f15bf818ba9a7aa7dd441a70acf28a05b413e164d7fcc76b4a4d5e3aed4850d008a6f762a25174979e65a6394dd633e81429e9696c5150be9857938cc052fd62430761b3bcf862824d47ab4a65129d234edf3254d4a892517a2e0a2dd29d79a161d5f5410f52e02950d7801b9669eed5ff9655760b6bbae9a6eda9e8eb17e01abd46e8996dca3d85176ce4a9b5c912c177825cd2a5524643ff76b242c78061c3db909e4de56f46d41eed9dfc279ad327c5217521a78628458dc9c0572f4932fd9b4a77c2f17c34c1f79e57b4b685ace8c449e6d041361fe67aaa328a376bf1a1d395b391c403da161eff48bea89546a5937be225db02e48a27ad9234bfa31137e112b43240cea3075e0ab11683c1350dae3b2bd5cf633c4cbeba298c24aeb68485344a349bc5c70e8b2daca1daf4620965a8e5349cbe5020e608a241c86a6c847e48e9bfd2874d064d9438a0301701560bb89bd54d92606c0804549259fe7d9fa41cca0b9721ea7ffcbe2c5f7991e679d68228f841b30533519481e337dcd890dedfa4f1cf208d55e0ed1381df8a43da170e12fca397fe2ee6dfc54e7b81a7a57160e56f28ac075c10f090f42072d88069b5684fe751f3ffa7d70c8075e483464ade71bd2a1c72d1cc80a331f3193f5979610a9070b0c0abc30180874e5888ed4fdaf87c3a27b19d10c24d9cb0683cfac0abeb0b9c3de3bc5d2268c6b02355adb8235519fdc3769fd86b1e666917c15e07d2cb5950b90c4583365ef6b59bf97c865089677fb054d51dce5871e983b62331d8a99703f1d75a8e93a41c67867e9d3ffaea566dc392acdaaee7fec471da24bc621288629814813a1be8bd601bd08557a1d8358f10d5cfbdd26d6bfbfdac4030db75b6a36f8245cd2f9eccdb4f7fdd7471223c52ff93ba50e87d50fc09afc5dd4f6b1fdbe0b7aa27aed956dbfd97130966d2a1976f3bbcf9a0f4e6fd6ea5b6c7006f9fcb0812afeb68f046944c1a017c1a3b09f478584e35b4f3788b9df38b4b7457cd23f26550d6623ac116c1850783cc3b740c5db860528915f74d2927b26d6ee855b301de46b9194d1d27c38d98f4bbfe23aa3b20848efda5b03eee7990e5be39edcc67d0d0af8fa2b3e93900e8a234102f39fc31100e00933552cbf88389e43389dc38f099aff0dbbfb41b6e7bdc8013c4acdf11e0801028a416699a24c817e290f44a741072ad259b49f2688f73cca8fd1c9257a656a0073b30a34788c47d91cbfac484b1da63cfa78c8cad8ec5fae644566e34cab24a5899e6802d100488fe48c89fd9ab1c62bc557928156025ecd18762415a92276b2828a9fa5381b29777e9ecc5fd01a4d51e180077bdb6311b68162ada44b9e2a2ab8a3fe8cfb756396de0abddb12068a19b2448bc3f8c19a81ca27489a6570eb5faacaeafa1b0d25d07ada7a7d1364ddd74541a1fafe04cfcd26ea9f3c8e47044dfcc72fd47f0e8d2e732463f0a88900db26831ed5e92c07146e1cd837fbc14fcd98a523fd63911eca70f6ac2c390506af53a14ee7ec82b3fe18df64b173f69653ada276bb01084ba31ef41555508d50af953ceb38c3bc748a9bea000418e5471f8f67689ae4a04091a79d5364bf2ffa507d5f64ae0be5c7c6f33a98b5c8757ba4ca2bb925530eaf2e89092f44ae181e3bb859843fcaf633d1fea7ab0f414fb983c3aaeadca53e759357ce9221ff7fbb1de2a20df8d64c4c716b1831bed837bad51607f9d36464204919a2304555d3586c9ca55bbb20cca1e1a6a8e6e14442d6a6d74ac1659f313cd42563c087e36e7a303dc47a3d3a4ceb684bd87e01c16d27b97b66af461021eec3f596df6800e1284c5a9688424349e05684d5ffee3c71f8ebafa85e5ec270722442cff2acb8460de8d55724abd355c37edc592d5e22ceccfcf909cae7d61e285ecbb44fcb2d69d3f43ed98fac26bb776f12a0a5bf0931dc59130fb68685e28f97d05c3a2cb43608a7a0eef84fa6316f493a53130328662802eeb3023d7f2f77b72f6d18d206f00089a3a68f178a646af2e761bc51cb99724771bdc7edb3fa1f1346e5ecb92899435d503060ba9428fefcc2353265a0ef5bf632b54f42070dcbc38f6b3ef2c7fa79e921697fd92e5a61e4d9c44f5122187b21191c8be807a930e59bbc7ecdcedeeea613ce0048faa3f9e919d23767a210dd000655acd3136cd8c4e7a2f5f58a785f902571f4225ea1815773480ac26c90947d68623b18e18bc60de8a48d466b917388faa8ef0d95e9df160e33463a6a847149a44401661eb70a6961ee7523f6b9a00d199d7ec2272601e1aecf30574cfc40a2252f9a2eb0c549893e8de546a03cbdbbe4d2398db1f5ae39efddc4261445efc71a62a2d42fbbeba2417052efead3d8f47058e08047ec7474baf4bbf77fd15aa9822e56050920e8a782246014ad36566b3ecf5395616f68763c245dc01d9ac7161a5bf635376a749326feb5c217bb6538c099e2dadf092d631c6a4e27386782b8a8ff246c429d176772d31fd7308d0c68511a4b045f9da472887c6875e23c6fab755380cb8c3998f24b60ffeb98ddc536ad746129477602aa9d63f6c67347b360cef3aa619db3bb80c9fd37115e67145dd10c82ba3fa4eae8ea129e63f32184340be12e960edfd1d0a481dae9a0afbc47364ed1a318862fa6ff1e1d521f5cffb9a383a3e65dfe5305dd82c08dd12e9c863dc272ad1393e222dac2ad7641cad0ae0ea13babc25617e66abd8912c7949ccbe0450c928458f8741d0680034407b22d9ca2fdcde2d5d2af1aa51eac065b0eaf98a05f13a3fe6c9c48f20eb89930d7a90956c80074cb798a713dfd2654c120bf80269fdc992300dbaa93d4cea43a47c3e69cce5c47d5a4ac38e12eb664c4fd1b47c383e7b4d5e5d4711b32e721117a736b9f718b077a153a93b29bdd2ceef8b9514d71a37e67e32752087acdd34144045a66075cdffc8590cd3c8e86ac40861159be99ce22ea39b8fbed8bb6a8e99cfbdd16b03cebecfedad8d828e09997ec821cbd445af2327bb91066d26750de312ac76ac9afd83440208b39353dd5ba926ba5be515a1aeb98685bf0e51f2be3f0c4762137f866269685277c488d5f9cb165b345070f2da2c61148d3f30cc2b4eecf386cd30c83d127184b80433a425bd2feb71a2d92d6fa99c2c58f6770afc8f09e6d7bef82d0d914ba7b2068a6b7b378f400352f97ed4b8bfbfef43ad73c1c058dcdc5e83435ee24c470335ae722647d947069c310c6d231f28eac370296143f366b33c741daf960f09be88ffed61e4216a414ebd8f8b534d78ba3d4eeb146a06f6db76909363f7dc40ed007e906cc7e4d7adec4365cb6cfb3fe8fffc9650c8a9bd75097875864e067133fd29fcec078cc236032195ab8f8e078b63e6fe40be725562e226b7df09e054f59d97a33212a866132202436fc3d7cebdb292ab8f98298e8e01f8760babd2fd63a22993a9c67f192852974864c7e8a728c640fab52189a131807b0e9c497628dd688dcb842b3bedc23062d5c4d6e470d47cd42d691561eff3b434d16495553d28980f5aa1edd865c1a3dc7a4dcd9649eaa95f79bd9a39d9c2365d53498c8c66d64afe66cdfbd6990dbf08b1c43d69c5a2806905317e82959125281439eec46e54afa3f9c5ce546989796588868af997f862b156041bb7b6fd08ede7296c6a410eb4c88a1940576bae9c18218eb879a0fc228fb74bca749896b0b3d359711a76fa4fc27da12c528a9d83f8e6b4bf2347db9260aa7ccb7f606e438a283287872f46333130a1d5c7d9c9caeaf3be4c60466bfe4faeb99ce1b7cc2db026f6f38f231e3539787fac64355ab01478feb4fd718607d35700cdcfe2ac08fff70bffabb2e8ece070905c16a08dfa4b110108885e33bdd598c0ea4c427e611bb9938f8216b5da734ff6808a50f9db4f79ecb854a4c75889b1922f0b19014d5b0d1885dc76791131f0fe908d02d24bc34b8d0685102959efa6d1593e0579ae63dfb949b9b2cbbe5422eaa0cb2941d3e0f820de167f7b4decb34b34afa54bd8911630a48bdb6dd15141e0a2766aa9f6d27fd988fdbd76000bb475a11f2837f7cab294270949b3c50810c2a84f3e61af7456d865856de85a08e144fd31a8084ab8e46dd574d65d1abedded0e1e5feba7152fa296e4d64cc10ffacc6c46222f6d896b9d22b72b13d4e1887b84d45c7e0342181bef924dcb755db1855daa9a5104edfb5332aa2099138341945780af51fbc4490610f44d92c913d64abdf6abb40f58ecaea12337cb890bef95255311c0518b587476309bcde5c41e5ae3861fead15f84ce68167506888b23bb748f42df5cdd16f062e21dc54083edb6339b60b88ec546f2eaaef1f39e61399f1cb75b6fae7f8eddce09877315894005923c87ca170ff807065d3122a4e56cae16de90cfd1bd5a44ed42ba2f391f4457fe167a753bf395fd1879876be2c547867aeae330294317fb3e02862ad657a31b448d1fea304a3dd204bd0341560f1466ba91db4e7d621598d140572e8778a1ee996dc8501c930dd45544d51f4788ee4c4bf4ca3e94341dbed51077b2c10a75c8fc681412f60de9ed14ca25e9b88e422c149ef1e215bb704ee61522314fd57915f008b157c78bf4787e9264f41d1c523577d6edac8b778df112c1a73cffdaa9030f064b32878140f2746504f130e2c7ce891c026e891660d9784e934813848f0914cafaf421ae5257ea97cea62290ef62ca9d56f9471658e6daa3de0e53e16c8f38754b18fe8c29186d3d3ed47443526b6596340d7aeb67693996fd0b9879dd8a8a92e13dce7389cae1f3334b403f07de84845fa03bf6ef243171625bedebc1621cbaefd154f848391025ef904320114a970513829f04a52e6aff3eae5d6b4ecb3cbc998ff4b0e82cb54700a7dbe1e6b1ac66bea2633222ffddb7398caaf3dbb1e987d1b31a90e98d53d4c850f8ad47fbb15dd7dd0b5d51a3cc3b4e3dec945dc73210b10e5accf6be8dda492ece62fe292cd466145e0b924da85c92844643c0e7c2925834faab793a7acc124537c3dd497efaf571d1f60acaf98a2f907464b0042afe7c4613ead48fe32b5d637c14567b50ccc4af4230a2f466a86de4b8d086f5d8733ad25dbe52aa315431e3b3e50a2d17012f57106d98799002b4515cd6fa26ce999d5ce09dd32cff97a6ff290eb46e55f76c3c4dbed154ab25d63dc5a033d7cbf907a960d3bb99b93e6f076f7a9ed934c31caaa14e61a16ba46d61b5e4bff901de8e7df15c8e9f98af28bf75af8bf0b872bb6f910a0b83ec8b2733a4e2808aa90eb75b1ed0b52da2c9bbbf00f81498250dac0ef1c2a20e7b9b14486177de09acc02ca8316ae22b86fa750cc2253832e3d67954ec59a17c0d78cd5c25078cf2c47cb9f227d36dcd286330ded3a670bea083d9fdaf8e3bf0d80b9851968f89171a3d985e3a474dcb1ba7488b34b661c85dc23f46f2a8d30ec008759edb49c775957a66e147ae241ada0412dac705629595e56192621be6d102b00767b5aa4769f8d1e5b789a22c38393651bfa21b635ba1e86ffe10af60994606408dc3ca9c1d8ac4d47e91a944f4ec0ad3784c48001281de908be63746ed128ae04f93f8bd7c00ffc69c2f97e25194f4283392cced04a01984d89d1ba445292ad527667cd97ffb487d99d8673bfd7a1d7349b2745b2206fb31b3841ed0dece2291fa799b5e993c523e3527f01d5f624a15ce7f5552aaad03f6947d5a04efd63fd9ab6008a60aba167238e01d02702f85e272a0f962f04190540678e8d851a7ab819f8fc2602fae0c01dbc01dd83f8d37c9e03fc65dace8fb1d41e80bbdaa604f6dc08f18939d59084053aefff0901156f5b3ee076c2f600ab1fd4be01db68d25b1f5de50b0fdba63845f904cdb691399d18fb510fe2380cfb1e4a860e0c5b0b79114601e19cf9d1ba97ba92a6c85f147523f67d57d532e325e6a55941b7c01f8c399d30009f598d960128b89f83ca336fde57ecda750aad2a313d9f993ee33ba2fd5404d5409531690f1a795c6b1534c61b59a05b933123c73fde6239a4004296ef21b33358f7c53238c351143dadb77c519c0a6248bbedbfb8360ab6cae68c9a8470ce53886f680480b8877d60400f26fd685ed1695d6de09c1c762baa417785ab59a811ebbb986a72df170274bb3ccd391b7e32e838f495347809a54a2e299e0dd3b8f5388fe9ec628016ff02f853f830617d719887c76a995ab8fdd8861f0f6c41ed4aea65743b2f3b3d08ec163e97870dac1a9573532a3727d89a1e3e1ac46e973fd27792a299cf224f00bf294c7efc418820fa4df49787bec93f9d7ee50a80dfca7e9c701bf9e5d07e9ce3deb6df2f5dee9e0082a2baf7d322ce698d4470cc1de03c70aeee815f31c2a1338c2360d2e739c2a15fe386f4ec0aa5cb42825ace416872915b83818af3ff90289256d2fbcce3a79f1fb28185fe423fb7e20799e4729f5ddb90a087a41b8e9beac413dec401de5fb610733edd7986e7f842e9d3cb8604063838febf38cbcb4cde37543ead9761c6c2f36b31eb2ed714bbb38e76ef94813a6f26db37bf3b373a9bf1bd96ab32c43dfa7b335aa6e287bbb873601718537a811fcf44bde19bc572c539dc09bb74514db7b3307591a2a6c6811059193152dbd9f87d7e463a88965adbe5fecbff7b6a0dbe6d39339e43a59b38fdb386ca3ffee2f20635855b64b81a86d1486a34563d83d45e1febd434acebc1eb900cd7a708f31185f405f04689861a333e003e46bc930562c588fc9d565e9425d2d1857076593af7ad081d9074328e82d2a603c20080ccde667eb469d5bd4e164d5123e6ef8cff2a267d56fc7fc0d383a2ecc4a3a61ddc38e94243f47d56137284f9640f6f5b63667cde28c22478f7f48359f1013225006df80d62dd167063e0382ac59857958e582f49c3fa68fdea459a536b60a3212710cf09d791647270e798877c253361856c075cc013b47ecb423d65e03cd19e55defe402e1a9d2a3beaf2c89b09e902dbfe5945cb3c7711de8e188a12b1baf19d60f95ea683f9bc1cdfffdc19a651c9c7a32c64eae13b14f4faebcd9dc7a4a57f64e6416c950da769f95e97c975ef22945c75f9a9b44b6d81647a3b38a08cdc678cba8831866e7a20e2ffb4dacdb336b5893559348b2c4e1a8794fc89de37e0845d19b16c34fd96e7cfd728876765b5c3aca56fe900a4644c7706b12802f68286ea49b1aa3a201f55e6a91c046edb586df7322c66d4186774f2b65667274b123c618b5c40474cb44a6db7d20348990b1b19287ca2aea5002b9fc5542cc2de343941f7e7e02fc2211b2ea06e7821c94b4a0be67fcfa5433a4e8a5746607da0c3710f0414e701c5ee2df6e5d99ab5bdb69229c61fec6d4730aa7dc610dddebf95181668c635963cb74e87d6735a2d9cc594e48f53c231120d5783f103e1830d5c134fe2972affd0699e6aba54310006a0f31f4a92cbe252aec9736d597cdd3a378f6ad1f247eaa3a03605a1c97a2cef033edd4ef01678b985e0a91c0f5a8660bf83536af16ae96fddc40ae804416d7cda345898a66b0c176f99f1a1e61dfa2b573a946d937a6b837e0316d1fb952eeb45125fd8e5b2fe89fa5e1fdd111081c0ba5f7fb0e41e21653a0092fb545b06fbbde08c63f5b48e08971a0a4437e2afc64a00bf9760599a915c8f46c145421f633e7be90e6725c83ffb7bb55d975fea43ae668f26b3a0244027f391546f0735846076adb217705bcac70f15ee23640ce08db82a112538375c8e702df3b9a447196be50a8405f781536980a0fb9e60b044e2917202306c7823294b1775b9b1d7793760f1a232066769e996b909068b4109e32c73732c3aa16405ffa8d819f2e2e26b21df30d29aef2521c29a0633798373b518ccbc1f82a83db56df589d1f09e125fd2e219ff2fcd221c751784642c06d54b0fc1fb6cbe228cde9887cc2abb126f95ef47b373e6526d689ffddf0c38fd7c24ff36b2951475e4a4f2376e37c9700dadf3d52924a1a903a3ea55a35aacfa64b0a16f8bd917afc1dabee6c4e4957163bbb79e17f8c9741aa0a795cb5f8fc332ddc65176432e633af75b98b5e58fa59e63a2a13124c3089fd647d6a0799ddf11e119b517586acd1fabc454ac2af6645da112afb4e1f53ebe06fdacd29fffbd4882a0d876844e56ef7e0c6849c13408785834912cae9a57bd393acf2314e173fa9c57ada2ba849db295656dc992a2c5fe5c34bb6da9ff9ca29939ecd805826c062b54fbc7565fc88d74f25f0d582a9e43f1dce687e33700228df94fbac2cd2460fda7f2739a11d0f299b5850aa66921a29b5cc0e84d64b7d1822bcb5d8879c99150df773516fc4993219bf3f57e533e1e4342f4bd3f2fe219a64d174ac6e7632a409d6257944ac07aba481acfbbdcf871fd69488e166feed2f54a642394fd5770ed5953784b154b61550b04022eac9a081e8692d40a30718b8043eaa76df1ea4f101e65c4e5b70dc8be8fb67f02c905715d4f3b61fe9f8bdeada763b2dddeefd5c3bc82a54cd8812318302be4806f49c5ac08786a5975eac2e5521e0c69ec3f0ba25c44965d4ea1e0c175626d184dc6e7401592af3842d224452bd05575f87c626d3391e60a929492d833b527c18961789b3da2625d3dd9964f1e41fb7da76a94f3f83aa8b951a22df0cfd9df84f1213b213bee98331bfd660a20c63bbd627c44b481df4d0e8f0978a8aeb703f5324c2f198dc10f1e21aa9bc4fc38e368a4e61be79e0dc1bdd659fdd1beadb8ef6f30e1c82e48a5beb46b981abeff596d27446322c8c4c8e798e4b7b512428ce9bd77c0217add9c7c018a13c7f305ee6c772f920cc833228aef127d8dd1ebfc180b6c0393d33e1a94f611997d5ae478378e3a6c8ec9c7779c6b1ff8848736ab7c6d0282e545cd1ff0d1d09d24f2941baa1fff6f45bc6fb13465df361293034d31f6661853f1d780f41628c1302df8abb5ff806c894e2ef978bf63385ac4a3ba0a63e043dc18d556260d7ab0a3e9955c2752ff42633a2be226af1d9ab7916838435f5bd6fd2272b97a8450facce9fadb49c6a18a8d5e7b171fa5e1bac6d439027fc631ac9b38e91591010f5df166dc291e86c27c55af5fb8719c71026a1aa2414090c04d7193fce58153b6548ea964c731211cc6b71dabad0df912d40b7f65779bbaa0f031904cd3769bcea221becfb2be61ff762a52547fdc95514bbd51086677e69650449f4af8c5f027d2c2ab959161d327e612b38202e0cfc97a224ed6c39ebc194e0a8feb72b471cbd7d0cec052e1d901d5e831fe973ce899a3e094f43d565258cb0177e9669884e7f03fbe94747925c4035d22b7767fe2eff571ab0034e3320a6f6d30ee5212fdcf653e58c436f9584fc6c64fc051f6d898330fb35f6ca5f978d1f2f5aef369e6f48975bf5ed19ccc2913a841eb001d39c63362c964e9ff47fb26b40ba907b3621f73663b0588f1e1a1b9d91cb19f9b7dbf44d815b885f52dcbe612b0dac491c14bd43a0cc1315a7a0f6b675562c20805b5088bb6bb184d4f0d1ae6471d316a29caf55c56ab86f23de6b92f437b4b2922db8b1ca5d0bb39e1d05f997793b7337ca80619e5b39eb4655ad5f4a270f8ea7de255d9bfd994ea43ca001881dd243c871308041ece58735464f0bd000138ca9e94b7359e34ba24d958ff1c60445fd68e724fe33574b6120213c1af2cca03417f2a8fe0276ef4b8d27ec305768b026b4d54822727839d127e3751ef1688675702bd9b1de61e9ebd5a45aa9b9087db9b0c7a26555815b7df48ff3b2ddb4562eb6ae6a54c0955e8c8af7f9c1a3704f7a9fc5179975f333e882f7edd3a0848ee75e3e37981f81f04c11ba280d87d49995f0d5f0519da623e2eb8e6f916f51d5ca39542cfac0cb1076c5a9afa0db8b1f9c286c84f6b0e8aae8a2a51821592182a9f940c400012472d4f2ba24644901f5ca1a3bcec626602c42acb2665fb1134cc16df49cc4ea9cc14f98c19c61ac4958b4333ef42de89449156b6da89dbcc4975eef2098f6a6c33b41f7e737c10704a1cdeb6d73c3de0efc34cb085b587e12f102ae89246b21140a40e97ee32d0f794dc49b442212943d349bc957aa6426be0d627bfb7f631647811d3121e3fc820301d7937b3460767325b5d0e7c1771a2c5d0f50656168982b3809be216b470662420a1c0cbddec9e7848411fa24a1a9d74953bba4288baf39e46b8e9a139e407f79326f97ec5d196e81a9ba76c12a6a663671e73caa13f60f00272709b50646cccbd8c7f601cee203fc50017c47c00cbfffabf2228a6fe47f76b213205c8b5986ee54f249006d87344320b698986b71de0ea7014fa6380dd7d6b95bd4fcddc10fbce3cd7de26dd04d884b77403a783e77f1e0ad7e0739567f2a6aa3075e1b4de4492f5a2ff5e90e86f9c6a0217593e833ab80fd6240034be53b0c262dbdbe731f8f82fb0ab10c8585e6e5ccf5380d4611f8fc02a4f84751c0305b5a0564cc9f1e290511f68771b6ea8058895827d43b8e891d8ba9e8f1d31140c16fc50377437f612864f06c3b0b65e26da0c2a31ac7ed2929a3fbd2d935e927974ea80763168bef9ce81b271761903e41a3c5ecc994c895af0bd2a2e7cce574975c3358c04de5aabacc879154b7f98c87e45e4253b66d657d726c162b01df14b0bc50009fe371501c63d2a587be78b8763d44e1d2d580c54deb93db44b546ab7a21af6800bf4d8943cb76a29c964ee83854ffb7b464733ba6cb108efc660f8f6311fdaad8bb08549b2542a3431d959cadfad46e25969a65f0511f30a517fda430f4d11c2f7713222a9c4b6647900520e26715c8bfdeb34a4152abf93b9c36007af5fcd5527ef6f43bfa1b358fb6b3a4092c9465f11fc0674b3304486d9e0e0b48794fcb27e9d4adf253d6586853168bb798419a09ab4fafb3576d2283f27c49bf85513e1ff8e6fda5a3630dd2cb6cf944b636533e685ea038ca87085b00ee75419f97f22ee2ee6e27ed1ac7d2318a351e9b730a16d72c8f626d4cd21ca05260273cacb2579ed5fffffcd6ebf55cbef40fa727f74719a913e40d1958aa92dbf074280a58c827314aaaf6c3fa9c655e113a371993a3d84072bf2331665bdb05b91187e024ae8f88a7fe6a3ca5260a08b0e147df313cba65944dc2e376dea387b8ba66df72e28b7ebc719e8bd55940ba6c8a993afb234c95625bbe66913598826ab450154e79c40ef7bac2fc3cee09f2c878d47df58bb61e5def9f0086d600e1419eddde67dc5a070cd5df8a79e548ec4d84675f2ead462774211c5327cc06bf1cdc53519f6df3df01541f0155542edbf7e1ce9b8fa0aa1824fb79bcfed32cb494161e92f7fb24d2de39e01fd57f0d3fb4a5d20c2a6695019658af1de6acf5b1234b3b2d9f4257aee84625833620801d051ac53fd255a61abf4a2a7457aef308fa3d0915a6b99837285e3537d5acd64f3bb6380a14a831abc5295012a94171a4e85c192c6d40e48c29ec7d8880ec491a2597ea2f608b6b31435fadd467aa5a83000edf80f06ce09c0a34b0c2978ae8387b41ea21c8ba290c4f7d62643a6e5b8f73ea7c272b946d264c89b887b146620425e8fbb11761ac8e8e4877bf24fa7f9ac1f286ae2ee731ca0aca06c6cf4ddc4c94f85c11a1d4a8ebfabc384ac40ca4e7f32205e3ba2153cc74e86fef861d10b7b3c54f039f79af5077dc47c4736b01c4a8078296fd5c3d7edd5cbc3232c22325922ee8228954a9a62d17af9d090b21ad2fdda3efdd867c96ba3802772655729278800db7d585cf623091ff7c68d969dfc76653e8534a9a4a001097dbdb63a2a7924dc6b17558714bd30d054d64b1d86d15621e1338bf1ca750056f0f8c5ab217a0b7923da95607489a3fa86368091869de0bb8d849a17b78366f00311fd9dae4447d746267fc340ed50fbdf02dfdf1faaf34ff29f9898688b7f58fe4710f0b74b18a4e21a1710f53fba7b10e58fd624fbfa28dca2b34dc82dec5f449e33a4bb65719121d21a6a12088840730e35f91f539f788e7fb4d35d8bb42233264b92aad93ec17ef5cb22cdaafc04b70294a2d018282895b55af03a4343680fdb4ca51cd5d0a4c039afcd765e84e7693a6776effb475b8c13955204ceeccbcf6f38f709d2dd85d12e887d1555e2d2b5279dd859018af2edd83c0ffb19afea7b31a591c7630fffbf07ac3f837f9a5b48ade9a1111fe2b14921f49e644be8732bbe92c580a663e91f607f819fc2923c63abcced11c4a9e0f45f74fd94e743e168c4377dabd7792d8ae46f9dec5a94873facb3ca13f7842d7f5a8f6c0c997da77dbfec039ad350ef22e28848e072e3f40489b939e51f9fe5edbd75863e87eac0569d32af35579c22f65bb2ebf8dfa531ffd1cef09005a39bd9f5b95876ae8bd537761b49ed2b17db4f59cffb5865cea866b37514a84f491f842ef4e27fdf8237bf000d39085d152a1dc89603f163f29b521a949bb3be861060096e57019bf1091b4f5671c5cbe951565617885af1bbfab03f27bcdf6508637ca955504c145013a397fc5cc3c9c10d2d7b8556605f4acef3a6f52757727cf51fdad52185cc37a518801e1390aef3030bb6f1ad7f4924399971c0c74bff4185e06edcc45ad923fccb862ed44ed7f6751734ddf8deb7d9f0411d3239d7bff94ba1afb3c5e3371db1c0d66793889951310d2325dd9b83c417253287501cdffc5bb1d8cb69f647cedd836cd6071155cabed02ec3c4ee640139396fe82be71c59b8be156bc2aa12573295d428507df6615a68b520b869a54c7e3d11d740f1e0097fa16c3e021cd7f09d5888fbe3c3892a5593ecc058ca3207fd923cc67d03fb5bf02b346fbaa8bc7e7993492e2b188a749c533da68e0b3a99667f879bfcc66d48d50a3e2b209478d72e054df35d29187aff21f571209e4d8d0ec753ccd029a73445dfacc88578da2fe53a2ca72507e065f8603a3bfbb5687f9f42f88e9ae6b5d5187ee4c553be543721ff12df8ccc29d64df89d745fa2a5880f21460fb216b8b59bb1fb3e767f05d99f2ae3232f38cd275f9be3cdcd9a0820ff774c4387bc4b31dd5f85897925cfb137b9072d308c39e959668f49819d871976bdb1db81a0c797f6efbe0a7eb499ae9fb6f259fa5cdaa8a9a7719ccd904e996b2285413a3fe3d67404f0178cfcf11edffd59935207eaab477b0b7c6308c3d95b5329034ba8708fe41b8dc8721a849f2b3734ba32eba038ad3bb460e4572bdf749e96370befaf69748968e8edb09c727650c59426a6004fe10ed743177114a5d03b0c35e3d0ae7053687d5bed339e0254a26df15801abb734b9608eecfeda7b0fbd1e08e782b39031a3cd3beb0d17a9ba9f3e87ff7e7581a8cb6090511f18f2418668a30335f8dfd93bbabf9dcd8acf56279dfcaf45044fe6e6c60839a90c3b0f987e213c234a07c867a95a9ab3d72b80de348a1ead58dac7a8b139e7fc68b5d5ba68621f124ffaaa80911e265d217317aaeac77d5e64f11e8f1f8bdf2d78848fd74ad7d309069df0a5a9b08ba8b4f1861c4ccdadcb5798ea7edf6d3a857024ef5476c69576d9398b1944faf60350190cbb3ec17f06729264478d806e1b0af7dfd2d44cddcca5c6b0dcd3ffcd20744172005a8bb4a4235404a66ef69c0df5d59a2e280fae5ec645222cf2d5ed78a6ec3229915277b1ac0ef26d28ac30dcc68c3dd09ea8808936d165f5d64f6ac4845c0bddeba97e3477027abaa391828b49d26756ff4f19c9f7cb65c1eff692540a22826f26b57f07a44ae14a69dee2673daabbb58d8564d3786d1d58f0d6fd632dd25ef9192948d95dc8edf95122da6710b852da478aa2d272e0934d4d612a7ac15048cb44b3747ed12c9496c9839e74b9fee580281330863e156aed5a7ca6c678f9ddd2c52cd5d30b30fa00994cd893656364cd010715c149259b28fdcddaf82aa99b7450536d0b93f4648fb4d7b53a5ced6cc8778ebeccb8045e98d3892b86bf5f100e8b7acd120bd21bf551549afecef0d99542f11c87e4471064dada2bafc530d6867f40a3b5ba476b73f97d5f1d5c2c57e57fdcc6022b358fad211ae07c15b30df0df013c3b096edcc759f58c39e3069bc39ee107c58f6971c2dd700177c84b5ff04b65e9a62661f559a9806808830ffca5d36eb826dd7e7ccd78b0d937e22bdae8256b0c28ccfa830a8576bff3c99f35d0f3006c9dbc36d1e35c765426f0df84058f541d1103ac5d0ef2526e1f53afa42dfd1d41908e5d6870a44c020e4de451e1c091472b64c8599eba33dd7f1bf88e60f9dca04dff5db705a14c7f6e7719f2e262eb9291c885623795fb9534d3fcdf6ae0f7ca7b308eef198df07a32a4426d5a2c4faba06b5b511dfdb2ec2bcedd5305da52926dcd85e8ff3a8cff1b5e3c3f128067a14a2e24013581a5d11edf1e786acbfcebd4f0bd77f8dc22d4cfde6a2bdaf28b6d94930e2f1f8546cfcf1b35935c7be54e9b457f6b61272cbd80e8d427b2b1dd2bb3f8dbb2caf09725c0da6ffb4e0198335ad36df58ac26ddbf91aed6604ce6f8ed06c4448ff6ce5ae945d494e04331c77130b1b9c6a2a91f549bfcb18e23a35991a1b1ee6939a36f81fcd9270296cefdff25956d03254b6b206cf28ac8a53a2b9d148dbfb5377dd936756853f0c790fa23bc35c9d438532dfe73345c96690dc43fdd5326d612a10bc440e7dea9b39b6ffb43c075597c8623e60fb3dc4c028ddb05e0001f72bb2995358451aa2d23719d4f69f029b2b801debc2811eb78090d6ce58cb52d461604ed642ed8c367e838b1e47eebb526ffdbc52e82849594457080674e5fec12a40e9358d95562eee9ccfffddaa2ee6891c2dc35aabba90cc8fe0d20ae06b47506aa027a5ee0a7e7a992511d75844a0e388aa9f5591e9d14f20cb6b98384f47135f2dd1e3b53b926496d4f7ebc4beb7a9d137de727f003c3a4b7e5c406307f2e4fd02fbd381ac0858375f5575f9e9ea870a0f9643feebf5c9b75a43c8d9c6065bad9fc9845b8a068d7483f84537bd35ef454fa73bb0254595be5183261bcc995a635141c51214067d53f57d7d898cf5c0e82b3fcf892ffeaa9fd83b4d1fdb2aae77fa1f82ff0ff023beb7c62f564da8673c6e6847d87559dc9a4080b3144dd8f6d209c752a6096bedcff9d74f1b704467fc5215fc1c1abd981ef568b5e47f3f96d1eb65300edd786db1f460f3df7b28edd46b45934ae83a1a422031434d14c4fa6728ed2ff3a2f0e6a999ac02cf1c1bfdf1d6dcecef88146380792071aa4ea9778b847351905cc56b13581145e776f124389cacce64c920dcb59eb2afef1be1ca82d4895c2418881f3aed7b5fe4306f5237712e05e0cb3787f58fb6b61b2775682208e887e28c3289c40e822677d2d25e827e1f549352131ddf3228b2e3f2d2fc1e40007cfc636d5ed94963bae4f7761cfecff2c976e3a5460d9de41fb2c5d367e1b3e674660175adcde0c08fc2a47f0ea9aefdeb0caedff74350191444ea14c4caf7af4f2b5da484455957e6a52c126f021a846f7ff564e65c838df716c49198e1f04cbef39e3b4adf5c21e14d1906acbb6b0160f9d3eb40de83477c9a0f6c7f6d9db2c38028197c96e82ffebdf537ca978a36af0bd4ee14c5ec16500d73e9a8b6cbb1f85a386a66103c001198a8f24c8cbab33ef1ef6faddf87732e20c8ba869d8b366138f50c52020cef9e440cbf5404cd10f0862ba6ddb36e16df5f937437bcf8dd9dc53cd2798e63cc25d3261f25ec2b18795ac5868ca589b3011acd74eda8e3e6b0c15269d75ae8e277204d5d1ec4775a5a7934c7b201e993d35d14acc54e2a735e722428b60fa535a1639f9e16a153199449efe09e00db171344103a2bb631ec9b103b4fbe19c206defc79fc9f5859fe3c29810bb4b9c62c3a27f34c44ba50f58e429103c243e6696ef787931a46adb3053c2845bb07a45322a7650885b622a70871566df475da94482a059308578f7d5620224eab4e25c193d2524e5b55225015e0d8b78449449964a267973422239a2eec6230ffa1c1e6685510ef4720d99c08df4145509e20226921f586dfb4cf436fba94ffdc277ba08fb1f9574b5dc94eed18456cd9db9023fa0f168b1840a9f56ecec52ff7efc538cde8f65a8d1fead9d052807cfb8568ac8187e661426d70947f8285723639598486281fed50f45862661d94b3fab7a2e8efebd4735eb8f0be5b0af3e0df4fe7cdfd28c2aa361c5ec6068b5a4ede523a0b9a95bf5c26b741b7485441f4de1a2698c3992cf0a314fce7fffac5cc7ea4971d8e06209b4c0a16190d2eeede0609ae756fd661ce87a2cd719aacb647065b46087b5bef089793c7c4fddfce82fdcae5991ba57d26412e49f81d38ad089f8f1f2ed432d3f59f896b9563841641c3e8a714de6db4cab80d2232c6aa7c51e075a4b37044b2708279e2b4c50fb6ecbcecccc54cbd2cb538d42c5d4c7de39222a27cbf4e8886c21cf602a80927b765c6591ef399c6aa6cae94ac6cf08b389f3b04dfa460d2cbf255df099aac15dcf894444606ff913c9be472d8ee4420635d712a318fa51d07b3c8f978f29d22ca8d4562a5fe48c9a401846fd91335f073992cb17272b3c26eba71b95705ee67b5549380bcfc8ad17f13c9c128197e569d329415a11e0655358868283fefb43afcebe895868cb152c017842c98a51031c5468289e8924a4a0a17047b399ca3810d2a17c540b4a0fb288545fb9abc20061562d644f31a3414c40586a99e3b3145030d03369ea0ec36f339763b1215e2bf312d9ec95351498720b9b8b369f59801eeebef6f432afcb9a9cb22c73c3fcf655247436907f2ba228457147ef1a3c895abcef9ae7aed4b63c2f88e1b343471180b4519f369d73b47c14ae7fc2dd28035ccf37c152e40259ba212358d64c1a07d5282451454d297e03893f175e468143420bc053480a41383110465eddba0956a784a39f671142ae6a445003e1aecf0c520f85032bf4de61e53eb4c25eab2ce6a884437208613e0fd3c4cf041fb5badce3e16e7c6bc30da5f9c47dc4ef17320fe2e34acddb12227f95d3c5cab1af23ca628b385dd872fb54208abf992597197fed4c87436505ec00e600c90accabc3ecbc58126369b03e50d70c9dd1c85433f4b16540fd0447c35180a41a207660e10dd7f2e50c4efc11180baa5e4232fa1fa52865000fd175899ed476a66cd30e1a2f3801884d6747562fd4985d8ffae01c0a6c71fa4c86c3aad2a458cc0682cfd242df3bd2897e19c2d9d6a7ad97707351a23c9dc0d96f07a00a047a734c729b46840f5dfcdee81298b9968e143143ab9549c4cad47d8919e20791f5c64b873cfc306a10242bbd026e7f9ca2c15e666291751a94b0eb3bfc950aacc23b017787f20ca91430fadd654b14630d33f1d3b1d1f3e8abd56d41cdfffaac173d73348c8a07261677b418f9a27080efcfd28fab7cad2a53db6bab5621cf066aac0a1e6462b309428797065e5b02c9e2ecab2695866cb412f66004829a853aec5d652c6f9b359a0fa746ba9d8211978c409bf7c1273e2e7cd5b26449cc1ae136c71cc0341ab08fb37a92eb6a69ff2691dbce4fcb3774b49e1a6a0be77bdead220cbf24c892ab712e2bc0b6a620c953cf0f31224c9c2fd60b61655ff5aed38f7a7ecd36c06cf7e9b494e6e2553680360c0fb8ffd090e813fe223bde43543947ca8d9f0fe0e431aa3e8d4744766b256954e80f73f385a9fd9cff43cee455223a568c1823c3db9a09c781d8c9b087e218bbb9ab1ff66b401bf3036fd0ac3881a1241b0570aca2198b118acafaf43e46340abdfb20103e12f2f84c2147e24eaf0ee74817202758ba8013353075fb036e8251a11c7055e907c4044f25c0eaa3a7ceacbcad8a5aa5f0fdf902e10fd8cc95a13f9e4a50a90cd74b0c905d4e9b17978aabc7c3e4f49d7bde4ba4cb54709b64920a60b84f9b32d5faf69794ae7587d0d1829c96226c04f5e10f97e70b3d2c7c2540eecaf7969f4ca8d450a65d994c53476803b8185ba368a2a43df70006140b8275ff7f946239a1ca13f316b11574df48c960006d8bc07a1b84615815f226f4af73e3628878ee0dc99a2e9930d1e2c99642129070b3f5d21db0ef46356f51faa72db8ad6f98999e374a7463c86d448b1ae76787f26a2ea8e38a4ad25dde81cbdac763751a763d0065d60c53e60390a4f47d08bc181e63a835943a8507487964adb70cc1c69db5674bea8bbe09428200da7e4f3420da589a71b6ecdb8b2d1e478a8606e46381e75d930c74e7bd2bd7f7de00efe6c2eaf9a01944485279f4e27a39f14528ca51c4870e1150c25c3739548a43a0f7baa0231eb3adb80ab3a82e15a43328e7b9cbfd239fe8d20c220dd53331745d8f9803362417a3d94753582f38f48e59f81f5d2ec8b2500877dc79c1d328d40efac5bb08413fdad95ba95df4dcaaa6f8a97a9894421b9be03ab1018631ccd3cf12c2dffc826254ce1e0a3a6d6c320dab130d7bb8116cba8e3ef582923bc2c501f813581a54b004c0a4c24127e8c637d1b051ffa0520d826a15d2f1b3f99c0e335ff37d5f147f467f35197b57a5328ffea3792d89ec4c4f70ad4e6a9af8124963b503c916c425e0b3e4382c832e375714230a2a871913998cae8ab0504e25e633ff97c5423f8ece808dee9c646b52dffe2a8777f532034ad1cc8ac098412940c8316b4b5ab424b47aae1680753f2adc4f7d3ced6a41a90cbe6cb91a7f9ce60fe766b05a82878ebe461abadb0db85f05ad29ddc948cccee96f2db9f11a160ce4aac3ed9caa3b008c73ff758c8e6db51b159ddc2d332a49d784b9bb1f6172d5c4576587df6ed65a961b4e96f5b11a675e45fa738f71c7f8e7313c987322083e2bdbcd8c7728d4eaf2235204e6170a35bee752b6ca0216a97859f6ba8e8abe666d393b70b397024493a377804513c44fcfb956cfe7a3b855bceba7eb89478b2a8d392d6c7718a4b25926664261b5b953e2d838283160c0c8060cbc2288bc5c70d9b3cb9c0e3adb26699e6761b714b69f34381ee0b9a5f84df6aaf6a2d4dc2d8fe29fc9fbd502a44f5ca8721afdcff0e216b30b9de8343c93761594306f893e304cf1e662dfeff7f23f3753cbcffb4709777f7fa07f06680a6ffddfef96b462353c8085547ea4826a1c3927516bdb89e0392ad6446cfdef2ab84e78a3dc3ca503ed4fbaba3cd520d68bcfc8c26cee61b3b8310c01b24254b811d32633b22f4dcadfaf5aa19aabdf640fec9892d186ff1622a1719e173d7dee4ad6818d49044876e1cc795439f8368b859f8d3bf976acc05e42f63fd2f441cb45b674013f38524fa71430deb546b83dfb884c985f4ee3f31e97186f95a239e9d10e9715fd892a73b713d374c059e7911ae675b509aec93f13fc95169cd30e5802d2f9753eddad0ed70f6c0e32c163a0e749ef3ba7f4c5d2b754ff3ce77ae66d04f69bdf43f06126188fbdd821c62975144597296ca9eeb719302a06a90314ab1f463e84dd0e3dafc0f3f87772f019dfe5c169271a365f7f8791bdd429df13af583051a93e25a510e69327a974971a34619f06f872e48b43cbb42a88dd3f34860f19457d1099de055477919425609c9824dfdcf26e1e82a7ee6109facab30954ad76e0bf00527a4848e9874fd09e1abcb1eb8049484dd48cb65cade90b37c1a15e473deac5b89a64f8d33be040df189d5648e8de687a2abd5bb8fcc2688b185e2bb1d740bda2c2c258382e48fd65b65ad22a728b4922a60e40694d696425dc8195a3500154521f5cf5134434fa8bda2ce71d4473f976213d0a80df053a7d19d81afdafe5a26bc8e191381d5664c368c784e7bc149a9ad13a7952f0b5cf4dc56cdfe8db9e3fef2ae976692741d1a3f55cbcd7f1a59fb40dac5defe80bfd4edd90a3623c8f01fad376e8f9c3f844cc49609c6dfb265fb133e2b497ffefddd13d49bd1dcad26fbbdddfc6b3f13663f4d02cb6bc92d13e4a87d2d108de51f751bdb7b14d2a6e0ded8969357e3c393609fe0a9ec4224ed5f7de27f00925b9e05799904a390a99bc9740e4d665b5fa5e17611d3846abf7316d86f85c4d3f0ed1f052baf59207e3f0ca1a69f554b4857df97b8116b00445107be932e43d30310b26de085ed36b46b173983ce77e1aa0811e8f25201371d305f43aae274e3bfdaa890455936ac125f0544602294ac8837a348e64bb09b373c6c3467cbc8827d21c033ef3b67aced2d1d6a88c6e8bafa916a91cffc6a6b140cbee896d2ec291512c742cb7450655d8255b1a6d0c10c1a4b38acc6296b12bcb3e4911137f9eb2529410745686b6d402cdaf7f6f88f225147d05318ce784ea2e04dce7211893fe24d1d5079c76dc3f84cdb46f17f1cc08fa0915447d0dedf7a9704771a532013bff684a6cd1928a52e236151213ca5d9696bd8546d2bec401ac92fccae2df4cb24b8a26f37fc2b9354dea4b9ccd154e26d2657a8ca898e49650611af11a0b89f11016a8b4a974e83f61060297e96f2c707697b2b42884588079c2e2db38b7bb8cc8df0a6fc6f1952bae3977528d99844a69140bc173a1114faee1d9e9a205c289ae32c768df823288ca34ab1a998ca927cb03a001cd30254e51e2b4d9c5b40afaccac55f11e24b569e0260ff1db3adedc21788cb59e1966f675f3f25dd9a70f2fb1a65e54f70fef90b56f4b153e1d95896488753f3dfe8042d51994037afd621ce0184f7ecd30018d0b7845cdaebec4cee7f47c0d279f023e549f643fa47959bf8c05a484ce74e0d1dc0e1ec133a0965eb6809f161d6e5b5009cafee0204ddcad8a7d4722ab38c99ffd88ecf3fec37d2cf7adfebf0f0b5080b57d34bcc4047f7e931e8ec98a36e46663b5746b8acd6eb1f822fb88a2d835c44b1817bc50664f6368927b386220e4305771767a3735e900aea769c10b8b4fcefbbeb80fef3baf27cdc2bed9f2a14f48bf979784271ae106c57660ffe87efb70aabf608b4c7cabb20eacfa567c52697a85744391d898762b4bb47e17ffaa6b8849875130145bc44bbee48f388380bb3cdfc535688107d0795833fb2ec08efaeab2d6598ebbac068d32a98046066c0b103ee7e4c27f37ed286155c84e698701bf84105aeb297ccac3a3e312a2c161ab2e77047025ae6974a2ebe845eaa131bdea4c6e9ed810289f2252fffd34ec3f9f7d658ce60c310428bd89cfbd6055621c6b294ec03b835484ddce61443ef936898d903bbd010ec7ee6e7ebadfbed7f1441e27b7878c9a06ab90f3f6c6dc5713db65a65bb792673b101867fecaed7ceff6bf00add8497eeb27c09c18aa57c35d1923a2dbdeb4587c73586f063440bc84a9d09b2e4c824c34fb57db3403314a2a4c19960c5e0b5f2543fa296b812542384acc673db0b982ecdf2b455e2bee613adebb2ee57df81dbf122a0745d1d53c91b25d506ee84ba9bb523d1609263138e7bea58e255549a6ef6d8e75d612c5922842a1f42a6a4a2ad8eeeedb69f2bf7c5977f9605877e686130fd2cb4d27f70ddb562cd2c9ec6e64aac957c44720ae16e7151b421d43dbff2687e6626fb95e311e95f33dd2b10645b38cfa6ef93d817a2c96e7186ca2b3095681798ce4cb2d4c5ce1c2bc5332e8b40b93a444c240df06d541963d52daede0d6d7dc7ef5eb216a8be943eeb7ea61cfe186097afaedee9f40041efb2c2a1334c2260f29f10c35112c6ed8b58320a0440bccf71f1bffd3b6ceb79792eefb9380fb8847bee4fe70197b6dcae6fabccefa41718108cd89b00ade1794cb35ced57f9d0b39196efe9a4654673437a4038e8d9d1673f83b683c3089827958a518d9a622c96fffd34dc20020a2a4d843d0cb00b0c0bd9f25629746bd099143b5503f3eede03d379e47c9949e470aec0c30441c0be8e7515e02273b7fd675a673c0e8db30d7ce09711611143b6de95c8c077de05690c6dd316f6252a64a426289f48c35d901114cf90d396a4fb43d73241afbda52485c5355f622895ba3cbbb5bac07765b044c2264f8866e997450a38cd6b1c234723832dd0116bbff6056ab28520a5df4ccc3377ec8398e2cc5c0e6a1dfc1be3eb2e3d07767c58403ffb4ad2fb6f84a53f3f13236d30a592da6597b0ba7bdc69264477daadda5e5db302ed548dc12f794f24672845c177cc21dad3602d213a9eb5e0caad6a05ef61d3664ebc9f1a966fc91f8d0587bc5b1f71bec8bf25d78c337f98b6ec32eae81630d92f45edd9a1d5971d643c9a190af430cb6a18749fbfb210a0ee793a0765d7ec4bdee7763ed62f0fb088dd95e627794338f7de2a0084c2701d13e0f0080ac316be16764c62c49cb538421f6ea17e26a92cc3ff3d55fca44b99715b6569f07caabb74eef9b776f55f6a64b870e867f9377faf97ff1c6e987c3adc7b8d224f4e81a3fdf9311be56a11166f0db4e66addc0c25d243c8388bdcb76f5255e78ffbb51773520255c0a744e2a7e56bfd8a4ab3eaf4ecbbdb5a6d6cec0b1909bd924c269fd6fba9d826ddd7984f8e75e21b3ce036a96f98eb4ff4c072ce2b42ced7421a34d35f4e31c8a714a1bea3d6ace450642fd8f1f8f5e47a054bcaa78daa24242e1da27bc7dce4b5516472b4b53919f74217c0a34b3664cf2946e180801ceddd85c177c981fea63dc3022bc976d527329487b8365f3fdfbd691e792fd3e916637d1fa6f56cca6ff1225ef8b388022f7a024e4ecc144399b1f1b4516adfdde4e062e873a977ffaea6779f46a7b79747829663b73d2dc11cff27950f2c8229170fb1cf3aae43ba9b04afcaf86f66efb99a37b89ab5dbab541401bd27f4650be9c5475ce6931404275d8e5b144f8dda52048efcfcfe54c9f2f8a06e01406ea585bb4a0438d3fdeffb03313c82afe7ac06903350fc8b7237e3336d51dd84f20fc5eb57c4a964cb0e5edbf55e5cb8e20098df11bfd07338bb8a09605a4e3d794852ef2c5a6e49e2e744668a270103b0c47b06fd13c79f39f5f800912636b459cd31fa39e3ef2eb9500bc5468c8bb950f77d0f7d03bc91cd72d5adaf76d0ea33a919eb0040d654535a15ce34c42c09a8a8d778b2b1a56ab41152a787ba2be76b9b54ba4342b61b46024d38f3e84270bbef334e77d09064f09339498a76d0e809da250f4b5f3cc207929159ebe8623d751036ae3fdc4b5e42b3726cb37830cd35c2f8df51c3d0a8fd8e18bb9c3d30f342ea1c47f6204c1a45e5e1fb3e95fc353b4c77e33d1239b9effe48313c82b21a10d493c75dd0676f378b6ee12adab69ea37d7a24bc66f262d1d6c2529b3ed5e0010679ba7ef47c1c5aa0c18e5d1f43973fe744f7ba4da187cde31112d2d78dce8ac142613e503f4b74b20a7b2743da2a830dc8fe829471996f29b05340be772cece75fb7abf9390fea5c63a1325cd51fd1a63695364886d22032cb3c11a7d15285252a9318fb26c0b1aae57af3094a18b0be8dc03b385efbb81b3d7fe8d0592661f421f0510e6b0d4547bff045ccddf03af0299bdf1e11390fe3b6194951d49fd1193a71ea47dbbb98c6912c6bc60e8f5f3580826ed32980ef8636d87de558463bdb44c824dde043d484ad48f3303b99c3eef15296724459bbbb857db64f1e0d078c392bc3ec5fb7c3be12eb4a872c1ca33cbada45d2efb8e111062eb508b683dc3bc5a8e33e2dc807883c0b7fcb11c2b8ec1e5b0a337ffb3c1c0645f2c3fd3421ce536438af44239722215f41e7122b9624de9fdfa21818871068cf7c980443cf36df230573311fc882ffa10ec404867ad00728cb6d0c97bdc3fff0fb5331856878eaa68e108a46bd3f5cd5293a9c8df6e67a969ebc32f4d7f78b825290ffb4cd07fd37b350f44a3f8b578b5e1e11d4cff1258e28f9682a3fddc9a57c8fd24e1996373e0fb74aa7401566bb4ce9ba74c845e9c5daae30b0773ef6181735fff120103fef0955206f985299b8eb7effec32ba21993b2fd5de9c1391bb2da103fa6c91461a3ddbe84dcbf20ba9c85402dbd90194b48a4a03bd311834ef71b9049db22e30c69693325511f7832cf61c9d9b635166ccec60af5d2e88620eb5aeb82080520cb33d244640106ccc6e1807abcf07f10cd3c7a662998b6b34109b496c3de7259b2d5d4204a94b2dd8c4af5a8d90eac6af9b785de0ef7cdc9a1aa90deed84ef69a4e23650348c7ec5de0ad662d0c682cffcfa3afd7eaf3cc40e094a1b86b6b841123ecf70b9ac2f48649d7d4fc06c60d1ed7142d463e912ffad3bbcd2ab51c217e56367415fd2c320640369f4eb02e5a119bfcfc224b44ee46e696eb59fa5aca54be9a3a914a251a578ff753980e05440c26269bf22623a753302c54de7f8c0e04a2adb364f11476e9bc9d8d39d2bf826e0edfc56e676f6f5c928676d779bf4eb415ba87c1e1ccdf3e375bc24ad115fc2b90fe3174bc998e74072879a9285fdb853fc642f948293d8a16f4125fcd5ee243480c962650502e8c2744f9b8511a1e89a406362d9f15e135185218383b2c314df4fd8e7496a3fbb7ca0b9e842014264a2a47aa2d58c533fcc00d9309fdec7ed09148a62fcfa9391ade43d1ec6c7ce2460723b6958c9501ca47ca1114e36e670dc6fd8c8c9ca263014d8a5890d31fe8ebcd19e615a841586308d1ecb3a46e2c5a9646416fb1b9a9e95c0050803a81f71e8cd7cbf2eb07d3bbc1a5cda2f9855eec976b45bdddd71262f37443d4fba90bf563ae8008eb95c9f1a6a44d855b7a5def6d4f5930b05c585e7296df214dfe6d7ff7f8a33bed0630537cff9e73f00ed371c223283208a2ef1e62214eadf2bbac5b29e4c2d02efeecc0ade1d151f8b16718549f877d4b11c70e37b0da648811d6a58d8f2f7cf69662c80c3cf54931b278aa44e8c7127dbeccf7aa949e47d721a41aec67f86cd27944828ae92cf65b74d3e172444d9fb95ff5e6b38f4a37f83f91b328d6c4cce07b1438fc9f7062a4bfa45523a44f86c2a82cc0984891ab5f810939698ddff77a2c658f580cb439fe6903c0b870221ebb5f448a034cad0603442fc7f8aa42dee13deb7a15a7f9d4bff527c23ff853fb3459195a12aa87d2994238c37053f18b440bfd1ede2c86f589d13378a73034ebd4ecf4e8c1ef2d6037abf52d287246c7f90115302258e6a20f52dae4361900f48c3b519b9477f9045c6171aa72ffa93a7d20c04828d078fcc227fcc47c0da39fac8a5133e639c606e6b7a32b46d57b6733d7d4202aa1a73f0f72ce2457ee12fe2fbe2fc26c3d28f14b2ccb30087c56b9f47b62792eca02f7e54137583c6353c95dff6fe679365de79e211b447d2eaa02da584216e63821f73c33b3acfef8b1a7a5b1ef6c0ee712f7dc31ac7b308604dec8160759c1a6880dcbe9be5021bc93755854d9624a534d09bb00285b294d1452348141a5a148f33ef7709417a8e1c6edceca2fc2796ffdc708bc22d730aabcde2388603fc50c91f40628c5e2cda118e47b7848151647ec343cab8683d2a7becdae4c2fb019216cc85826d335b0f654352394346bb651f08f6bba9f137b793caff3b8373b1d740eb28d4ecb0cd00d59022c357da3d18338c150442e2c416d7bffc1b70306c9a2d2e380006b09060fc95cb381a8b8d990f9bea6d7c43b1f889b7a952755b69abd712f51c860de01e949246676ca91ea30c3a45ddf0ff4442b59e048e5bcb08a6b01972357d537fb8d1f5a4fba8965df2d05f0cef9a5255757f36e2fb3f5dbfb8dc8399fdde1918b559d3bdfa444785ef499810986347afb867eb766a01439e00a20b00c634eb2387f29bf7588ed78a2091dcb54c4e071352c5ca7dbb1ed41c63713982922413883b5c8c52e493bf2e4540886b64eed48110e01bba10d32e7a150f21ad66aaab2621c80efc7918b3914f7ce489aa0d592e1701004e784a8f93766da0f2843e8076d6d47c18f21cc675818b1e35fa6ba0b534910d34d2e42884310e73562eba9fd7d65dcc23fc43699a87173088cdf5499963b754ac9a9e6cd7e98aed3716eae1b23116e29c370ca65b81278953bc009446f2cc8ec85a7957d157a6e05e9585abbb926552da170a3a57fd3f09ad5e8b082c0dd7f19416072dc390c1131989c0c9f8e699b5ff38d611d27f5a2e532b49594a4a5dac06e890be23b306ad22a3a38ffa20f0dcbd6a0136836fff99eb9248abe3b9cd128676ac3950012bb6b1343ab9ef40d26c4a59b9c0131c10a06cef5d6f1c37d0bedde98ca8576eadf9d1ddc21b11a7f2198477ef42a82cec0f703a2bfbec57b3ab64fbbb7351ff071557ab05feead94bfc1ced0efec90df304ffbe44c93c3530d449d9651471b501528f3028b3517d2e45d123aeb6bf4af766f6fe401d836c121eb32d523943b5a4f561d350ae4409d30492c1425ef742f52fbdc2f07fdae3111b750718ca49af7ee67add4066baacdabfe386ad62c227940bb6237ff7773f6ed1554966f0df829e808bc7ae42226a5223bff04724b1618c4bdf9e562bb166bc17ff8cf81fe957a5979ec17b7e5488f625049e51b87f7d2a5c6c4ea886525870046b6bb3ad11dd27e8be75184c610f0b88138b85d17653e17688e0ba97274c242a4f9fcfbb5bd2570ce8662ef867201ce1d8bb05b0ec4520f42fbd6072343828455dc797066691b8f6f85e8bf6f3cce9ccbff35f4dc967325c33eed6a6bf9e8ce953465cac5cd4aef568aed792c59d482e85b414585815cfda3ea1eb9f82eddf0439baa375ac2b9bd47423ed9360a792c311e7ce9bb2e2b35df8f2e22dba0a9a1befbf77a4152adbd984ea71f26db47210d5bda171c960f53ebdd5c42f7ab7732cd7ca838e1b7d80eade188248565997bf693ba8198ab64a3c0bf867c7770298ed4375b1b43a58a0ffcf7714b15d905c7cce9bf502083a8fea1caf754416fe2e88599aa867127aa77bb3e3c74330ad08cc2a0fb898e6dc59f06fc7422500b7c7670d794665db97dfdbf53277da5b49e0df8797595f5e213cb4036cb541e6ef82b2bd3218035f66089842a0fd3c339dc56157fe9bbf10694a7033bb11100ffa2aa547149b37a960e0156f8a4ca52a05df078662b6c048963e2bb2e964bf8b5846e4751ffddb373a20d4a7e3fd3ab923dbaa9a2fa48016d352e02b09ef0455c73f0f06d6be8701d60ed333c0bf13dfe1b82ba7810e91804903ff8fa133ea6047714d82e74e1793500a4fa3e102520bf74ae7a644bc29eb064200263447ae92f243a6744c6cdc440b56e15a57deacfd6f7bfcb40239c4559d305740e5193f524f217f5ce43e8d85866627a4337318e3190f9f4a1c7364f083e76f66672fe03a0a73b9b26aa45a3cd52d99ccb9aba277440c1220141d23964193b0bc12c06a227fe92f2e73aa1bd0fcea70bbc53ebf34c46b68acc6679c688fe6038892d6e1d605da889f539b7b7cbad646d72f2d8892b31ebea588965faab882e5bbc056b0341243ef1195fb716f19f0b52aa37d40307a1bb7394139026c11221f989c0ec2a8fffdeb1bf8f7b3eb2681343e93934a89df528fec8f40334fa75fb6b7e71123fe3240108601e7351a6def27fdf88ae45e962022d39b9e1dbd3ca1bf4fc67f4fbffe59855138f6a63797f9ac30e2ba7752f8632f609d9e243bd365844104b96b577aaa265a6267af55a7aa24286fdf01d8fb37a0ef1db2de1e3d83f9fd8379a00cdba2c0e57478c953065a4e5698fa583c2433f8f289fc47ee39a10bcdf67d64244e9137f9258dae7f75499904f9d10aa1c53d1b3be25764c8078d6b6415be28016b14d02a3f4e443a42c885b4161bc8cbe8b1baee76752473c36fa3d0ba085e8c2e880d105db6b08958607674f58804f6cc22e1d8d9b08458cb5d1ffe016eaa40110502f414aff45e39197ca2495597c81ed073c7d3933de17fc3b7ae704216bb607fdbcf78adf807f94b6b9081583954eb4b4cb8df945bf34da40d1084dd66efda0f3f433ad3b4ee9e165399c7ca792c025619b87c73160c2b940f44275b9203bf428706776e05fe3a37c62f6db3839918abf2de27c062fbe335141a43e26e9f80ee58b6fecb6ed5112d4f42c15762deee22f44611e5df5c88a49cb81c0d47038d932cd453f9b767f86387b2bd2c4511f5ee4b09bd1e5c7d35e4ccad0b083a89f5ec0130a09a93d33b2be47eb35c94c4461e8af51fbba6721b7faa05bf82a8231197fdee2320cd9a535f0e8b3853f8876b6a6d338a0e16f329148b1cc3f18141fee5477f969497c92385890d4a98774c48fb2ca3bc76a0512ede4dad728594f44744bf4010e24857b326709ad94808a1c381e57492e5082c10b4808143516482043c58c4860b36ca60809ce59d6e21fd77bc7500962f3f83c6da5d7008775bac24ad32d1169332e4955d7b95f318e8a04bc900871c30e6f77039b0a167d4e830ae65c7b9ccab34dd7d389f22765b97c213dc4ead2e33ef181ea5a6e8d13b3e8aa512d5a58d9b4584fe700f45fbf955819c2226e5c5466f4d3a25cd4c7f4746ea57cfc50aad68d64b3076d7a4c891c5e98a10391b2dc7f814d417b93ae0c1a9fa9a9ca9d420ac7f7718dbbdbfacb5b765003496535968be0fabadce84ae3ad2a2c9f5bc017a86a9628a35034da490d5cee954e31423c78d66e96182bb3b73f0af0036565dbaebd8becb597812a3a15aaf1f5c9f9ec809ed2e3f88ec2241a6362140f416973c60f33e58864314ca9956628b34562a63c7c23c81fe53f916f633951d1a6dd64843ee0dce4f7aa80f9125e6a50aa41ee87deea95281545d06018674d803a4700741fec570c016e517207a6d37616bd22a23bb3287beb101dbacc9f5802002641e68dd0cd4347a5559af124067584e9877643485c8591722b94858bc043dc20d180f78bb978d9542029089ef74d2afdf113d2a2a27315ed36c01b1924ceef07330edef7ba038c17068e118e01fdd045c0d988447c66a458661717c9efa843d5060b7622f2627e4d6f67e7b1b802a9433d2ef2e17a77485976af9222e4b69d21e90b529970e991a00d91519d125fdfd62d080a7d2926a4067940c4959a957404da3f363d91b61da2d8fca69d0aa7480551e82b8d602ca0eefbe2809bbbed85f7f9832e2f0aa8ae96e8915380bb2fe6dfd045bab23050c9e9d1f060657af531a041469b214c8f28f328a1bececc8fcbe44edc4875b057fdb69a089737ee30671132c5785d1379598861e16fdd8c6621f482ccc4b19f241c04161ae074492fcb5a3ff65cea5eefee5cffc3b830b6e7c0c8a0d7af8289bfadb09231577ba0b4d2fb905b1e46661a79664ac796007466b8c7963787c4abc3613ebcd9204495aeca319c909e17eb9c3cd81256d4bf0a3032aab11f70b4331462929d334a00c9c7e0f7c2e125c466ed63149b19be8ccf5cbd2319ab53ef50a0a739f075025b0036c625b190383793cb6a24e06d066e97f541019ee2b41b31a2a239a2f6d0bcee712437eff5f94ce21c9e3b1b82d2e454969df244812ea5b69a03e011c08beaa71feb51501afccaaca150af8979abdb346b7581d1f95528c5bfeab00f4a06d69f3fcb1d8a573d05faf12a6ee68b772b217a6f5f18ffd9e8f80c38b6bc7abf958439d54d1778ba1b0bbe26bc1512312daa200e523a8987e8c017097551a4308304abea29de6ee45b563faf2f5f75892c5bf9f9ce75983c9759f17030ae0ccf6b28060789b8ab2cb0fc616c28aeac45835a398f0c0dcc7bbf6b33c88029e5cc7da1761b6d73fbfd65e259dd6fd3908580e4ed0199a69bf944d2aea94dfe27edf2f2f760e8b98d9698674e1eeee5862f71e24ef7593533bb6e3bd1b8d0bf07ade438ff3f82d5ac9e373f2807c2081e235b9e4bb92617e222ec4bf0903bf538d1769c7c426d10b404e30914e86c5bc899531d5ee593509762dc1cc5461750c042549fe743e1e9ec70fed3ccb1de8e43131ee0ece883ff9dfa5927aa0c485776863c7b5f2fc6f912b1979aa49044806a8b43f1b947106d9a08b6b27bb5881668001e4277d2da3925d3682d6007f19e0f3b3d8571918b7a652235f5d90a186109e98d53d8da019217cc007719846b48a3b98b4be39151567532dd32d9c33c00859fd132e3f2a4c5e327626ac7c604ecf07328b413d9c93b1d470e4fe21ecd3dbef6b3400120c60842946fcd53fa144dfde00b2f0c8203427d7f88bd04986143cc633971b2c667e36e9d9bf4d808464c496cf82ca93fca40cba3e63ebf893a495ba46c419b4f8cdaf964b69dae79f897dfb2a11cc08bf840ccc5494ea9f602489a714947d19ae479f3edb0837fb9ff83814fe1990dd5563b9e0aec7d5751e4e9dbfdafd70a635a6dc6d9d59816385fafb9b152bd04e69bc6aaa3cd32d2d064b6a6864a873335762ffc298315442cee4038ecc5f9e7ab3a0d620b2cf27f95e1b66087994abf1d0055bb97dd1d3edcf99dde50dfbc7749df1f1e4a4d68946b0d2da67f7404dd1d8947b6183936dcb3b777db723a23c94fbbf208eaf5a65bc35aa9770ce67e9885e6d6d3e0bc5c89261ed302fbd24cbe7225cd3127f6eb13443001ab4b9be680e8dc81ffc03ab6610b3a23c46969aaeb5b63df4559d0df53d16c3cc48cad7bdafd29b30edf1278e2a2d314ae9fd13e6b58c92fe95e4670ae9c8eef5cf52af71bee3921f16fce53f426614d6daa92cfe2bbeed4faf52eb9a37a81b6a3cb3c0c548290624697cc4215fe013b5061686e7f918028697941dd3bb47efe8b8507d4cc8dfeaeac7f9c7002d480230441c746652559af95bbf3ca925e4c20d5a2ce913fda3a9a071977f2ecc9cf39ee98796df929a26205bd1f43a8ebf3f9334275e9821008db4bbb4c4a8f0aac0d3e54ac0e76f99a22868e62a7d7096195f4bf0d40cd2ec46ecfed514172b89eb63640211a146d3a0f8cfc82b2f3455afef2c1bc0446cc5bcca27a3545fea4777ed6ccb399dcd719d5f5a549b8b5246e6ff81450ca5fa047de9c6f6ea8f93c543596686e2a08fd86466d9c58a1d6babb45181a6d87f433a271839c56a33c4c55943ec7f3115c7ff4eeb4ce04e76be29c56fd165f334602d3e433385e9639eb53847cf3dec9fd0d3b8517156a2ed97ebd5d994d76942f6c57d9e86f8b740c8f93cabe45608e9c6479be60fd8db0576cc95cd06387b19229f75bb82c11fd32996d128046ca12b657fb7f54b91dd2b66eae812bdf971865003737ac07f953f3ef1c753b223c0635c23d0e15fbea0088748b3860ccb54781eb04b4957e851646e5c69893e2f3bfff7b3221dee358b78c7887865d51bea7f37fe10b61f13726acdb994d0c554d43e739d131194b9316286d37f3d2b12e75f65c228a72b4382e7c383acc4b4b670a2d9287c4709ddd0f8424ca6b83c8336270f417fc6904a57440162bbbe080a1ba034e264692c2d298d5b30ff50c1abc5b911ae11e34cf1e44c9a7b7d633f5e94950463328ac35afaf3e7fd99366d0fdf57979b7fac4441218630a7249f9d1f27872ef8c7972929fcb5cf271fd3f5f986fa394878cb649c1114a10493342fd6a0356fe2b152089fb30bb2f4f226d92f2ab9d6f938a9dcd487ff9fe624e49834a3c527b5434da89df3193568cd524b6ace77a4d69f6d6e14ea24e87f3093c1f438a3cf267d85b651f1d7cfd7e5a176c9f5dcbbef5805654c9421e6380597ed0d6912b4ffe88e2a3856a133d74a616b7ef75363a9cd472ba9b17fc2963e89d6146700b3b6bc31069d993f4d7f627b654801f91730677a49295aea30ad746b1fdfac6d54ac854665c4ff5fd3de3b957abce291f0fcc2ef47c748301ff87c15278dcd4c4b0c07510d8bfb400856248ce22fae0b2061aaf00ace98c724410cfe3ad6068633dc514a80652de101b62e77698a7205214a46045fa2eb18efcbdb865815c347f0f7f5fd1441dabbc84e62000c200824cc1e5c7ae60bbff0f3a9a5f566f4e16e8593e0896c4c9fd99a528f462f00e86435121bdb85b8329497b357ff1ba98c06442b963fd05217f77c914542f472ec8270d17aee2728b86d9aec4cfa479a513e2c6698740e4a51e8b1dd66e422c821fc12ef03fd3a6ae0ed51adf6bbec7e5ebab0abb2ea26db200b7385e8b4b1f4efa0576e17469924361114d83d3591bd6ee5c8743420b986c96356900141d76d77200252e40b9d25f6effa02483730ef1514f185d18bdcd27291c3c31a2d5dbdb2f424fb3fa15b24524c10b6e6dfe114a613b784129448ebd9bce38198714566d01adf2b35f84b249efe9aa551f41487ce3ea179a7afe7fa990f8c019a6e47bca1571576550d13c68895bb4c3f4e5b6e90d2ce3207cbb9225c6e3d6dbdf3dc9c0ef31b2883fbcaf384243ce5c79cf9743b7dd3be68c81c6fbee822b4400cd0d49c871ac42b108953ef2d606f11a44816e1e02910925dc6a4c45513aebc2703383485312dcb6443dc9417aee8a5a81f10611e10f37823281b67f7b47dd20e871a0af0694c37023a4f4e9a4b80d83ac65d73d63043a701d816be10a86c8d1c0d8c148451dd8d59112b2cfdd05bfbd59c7a06f56bf5c5fb2db886d7f1813331cb1d10dcffe6a3d5410edbf648deb31b84f6fa458daa811e604e69b338c6507f22fea30b62ba5bb51ec018dee398affee7f7ee0fb583501c9ab4430e11b2f2bc6371e9797389b09466118003d2e78d01009fa6f43d7849eb21ad8d0e9b5bbda9e9b76bedb6235f10b473cc29c1940c2db05c56e1130d43649ac716ed5fbd33e9edd0075cd5a246922a786176a053f86c537d28b2c9efbdf83ff49f29c480676601949c17ba73d68117853b6b2393914f46cd9cc80f9cf268fb2865cbb8ce13f5c26518c384a47b4ce5b359cd636ab32170bb27933d0b2477875e63f85a4f93a6100d68d4636443931caa51b9b399f1da0d4ba3a006e9bbcf5c68cfc0ce020f15566100c655f23846637775bcf822f2cff7ef5d0f5b1a05e072efd99ad21654d244fa0d2c44f03515544a664481c19c8314cf21774cbe63f597fb3bd93c0bede86ea43752355ead639f6b757a0c4c49f6604215e45006097f8c53a305df733b78dc66bc2ee2b849a5c07f5fd7b34d3241f23c924f3c676b1a41a291742ec9e247dd135b769c9b7b80763b827b37863146d9f2f4f13ef907957c6018feee2dfdbb1f0684f9ad54f29b913a95187df1f18f06210edf1c2630b32077535b95c6bc3bbdcabe5ffc94d01fe75d06a289fa988e2c9e7eb5abbef930a26de8b34ad5d5c99a3abe734a2eefdac7d974aa736b00aac8dc3d960f83c95dbfc7fd409f4e0e2e3da592b4a34bcf02ce135804ffeb8f43b78cabe5992727eb1f63a0a913736da88e0a63edcb1113a13eb7136f7ff46119b5ee23e8d47822da8eaf8dbd451d3b9a5b6ff5922ea3b782ec002b0ca6fa1ec1bd8b2aa972e27852232de5cb4fa48a2979c6eac1449f67b399dfd8b77f02a792ae66fda91a6c6e817888a140af0562002e09e259b9db80bfffb39d029865fa4c558945c030f507f037e77bfc8443c8d800b88ad1c13d8aa364cb61040f8d010be7b4c893783a739b3c9f45dbb119ccdd9e456f11c553980c8bd107bb853a07ff9b586904ff24eb1c4655ca8d38b518e42a79637852ab9a573236d726cdfedd394ac0589d13c0f3f78fbc203b8d19e77b53234dec118e01fe42f687f4ade22cf3a68dd941b62e4c5189543a9f4d05be7c15553f26039de84f173445e381177c3a8c80b1a5e0d13a585ce96e50b53adfc8f109a9037137ca8bed5b8ccd46e85302431b69b6fa471fc1c184afffd838beb4ee1be72ada88f63970e140adacb05a06ede2ce04a9538ea7fa07deb797c4e6fb9411ef805fe9b287a663dfe7fd2bfdc0e61e8c7df0e113af9c0778eac0d63449830aee78f470c946f959b7f434c741dd7d60d2560c7508f78cbb3b67acbec486f95bf13247273adba2c811a61da87dc4607aa2eaf7d5eaff79589ef82c1adcaf788fdebf7eefbd504a322a6e1978a095ac2251ad6180a9bd7160db953137c380e5799bd610b5091bff36498a86fe07e1dae6386f097987d532476a70b6a51d82350b2465eb06f979c3adf4fa1561928243044d069288de875a12a73249d9dba92617b389801ddda74dcd00dc6a1f7ea1c3faa71d99ff7001defe445351c8eac5b202811812655558678e2a140f9dc2848cf0f79254e499125e186641ef3105815a4313b1581bf813ac5bd37a3a0ed3650224ac97739278fc967b0b4113d74477f80c94471fa7ff8af9ff615429327c289f3a1757874b4320059fb5296ad3c556623f96049f42b0500fd89257ae145396e03dd5b2eb0f0a9ef91d3441cd768956d4c62ae305c08ea7f01524679b00a5a15ac251b74cd223bcf1bb5805ca43e071de35d2fc06b48065c6002397bd4691099fb513837e21af96c2cd638c9d49f1e8ada14233c600aa872e8556a94890b47d720684df826344cf1c0225107f9455d4c45718058dbf90b61850a4d7f57b47a3d25793f7ddd1dfaa1af03f53e521304cf4a63d4259e683bb60eb00aab6a34bece0b5785091aa83c874894d2013ea5950a185c0807448a62295dd196ef1154bfc8977f788ea8be609b6f56415c7a8ebf2f643350b201f1552c4720f053386e55966dad0fcfd63c5507a029d81b38ec0c5b7e90916adafffa7318b086852a36d2e9e963ff3ee9a0b3488e65df7d894391093a37eca543fe3b8fcf48f679d087971267014366e9d51280d2c1f1d56c5cadbed0d1100c874151ffb5a74723abfe6bbd7663619d5375d22391d31a6a3b1f6cf5d240db5b9bcb36fff0a2e7dc1a6d39bbe275e1f35223d63efd4d594fac050e3b7eb79bdc090f7eb29c666c7fd4d76bce22def7d9218fa12a75344b6e10782ffc5e1339e8c9edbe6912fc1f361748f03d56a323249e539ee7d28faba7e5c639441f54bc8ffe97e5ed4e10939ee7c1dbd3fd011f4271dcd7820d56688fc094b3e7d44b0b2f24bde3fbf3234edb4c93f920f4889c7e09c1d6dbdd8a138c638ec4e3a52b46a831e58a647bb2b4ca49cddaba301b848229e8ca468c449fab73d84f5744900bce98bd289fe7f2d46158d65e16a0f925ac476f6b46bcf52f7972f1d2477de2a63d1f9cb9769fc40bc5fc8440c458a18977ee00a1805a2015df979d3ebf318b7df485464e9dd3613b86faf77758f612d26e1d50bfc828cb0c1da1c6ba6808d725fae0522f243fdd5e97bd6e126fa56dec82b32a690fe1e89c64bd0ce42127efb2882dee587a3669846c094acdb8543d736f71d7363f2e5c040c53568bcc7a49520880f8e1298c2ac84045a2e96aa33ae886df5af218031df576a3889ff15eb74d6ad179f4cfe53b9ea2a63b3dedddcf0ca670a0710c6f7ae5cd6d39a83826bbaf041f2d903a7390df630a4b52ee5d2fbbd4e0ed45c948df67970e9c5905ab16276d748c66e88538e8f9ebceae0fe5fa734a75d6c225c593bd88232595521695a2f94d0dd431b924a9c48f50144b7ad3019f03d771efbf5a9ecc54e9a7b951aeef263584de89cd527a4df63a7fc0a422d5ce9dee34af6ddde3c2bd52734c37bfc94a528806e902850a3986626ad76a0ef76192c18b56c51e25169b2064039148e976512b26138fc9359ba1ef715e1668af3a67bcb4b572a34894b07270fd46dc297d75123180b7e4317723f80ab187fbaf03387c7d0f2925f3ff3438c66f41672b023267b005ac8fd5ba782b7d9cfa5841c613742d0cfde7f3bfcf2e02f5105612d066b5f395de08f001de20d420de146663f7a4e1b537dab7b50086c6fad3cff1db89274fbd07d6ed16aa6dbd2c5ada51c0900e04497a480f3d9f54acfc3a38a4bba3af5495ec10c5a0cd3a686ec3efa0d991ddcbe3498e9f2a68c254dc4bf8ba70897b1e94f399cac4cd8ba4e0bf30a4055c596b2a13f24d0c859c1840393a562ed65039e606b86a7469681e436964287cdf5abe48e5ef983d8a086e74b2b1bacdf28c6b16753990f6b054680e9567b507b95c1e45ed43334dcab3f3e517a9edd17e74478a8622fe511ff758bbb30e8e625b00ee0682cc4cb606000ac68ef45677beb04ce32e115244daae104bb76c088ff8f9d138a3a46e43942ec2740a2289c6308da701a72d481274f10c4beadcccd3a4540e9e9250be522a857ed820ce6d79788d1359d609e48ff3d63d409d0027f0cf15169a6f70f24d890f0b7d7bbb16c133b23215d5068998480b4c7ab2589744f3ae45d92ba94e4b840d40e256a55c0d27c089796f7fc101d457699fbf562b2800f810ca45b15aa1092e9e15028b385a389268199493665628a9e32c54a5d9e3155e7b768bb33169f5555755cbe009b6327b82aa9c4cce11e6ca43292e3024b9a2f5794904d51f9994f851c58444c4ee85950120ae89a64a209fbc000b441c6b18db7856d487f7d7306a735dbced1af754f75e8f77d93426fb17badeb80e82471093dd8d339d9bf11573a8626e2aaf4af97a5ea612958e33fb702bed2bb43676b2070582b580597454e7e98583d6fe58c375aa5a2795e93b96cc3e001ff2fa0df8fcddaae70a1ab9c15d6e352821f3d7e4d4f2ece6646a65ec992246b3cd5c7597c084bf4983a3b5ecd5d4daa7df568856c70e4eff6063b673fd658bf16ba31e0a10551c33cae586ccb10d42c4269d1a284981002c3eb143aec6b705ae9612930ac42d42ca13897515d613490db1aa3f8c70c3975e6de5d59023b7081a5fdeb8cf92eef40fbff6f71bf7558542474c6cc60d603ece3565658f0af6b79d60aaa29986cb5281f6f3a74a3c705efae93cbef884949a9ad8e19212db2390eb8138c41526a79259056b5fc91a46d74467f66541baee1552ce2c149cbef06eca698e02711703700c72c2941791aea5a10d531fd3fe435f103ba61576353f2324d56d61a205c45e61f7160420c5dfbe2f9f65bbea15c6a734aac427fb7b0127830a105c9e40b226ac4cad7960c7c58a31cabeaf9fd58d73343f44e6e5c357ecbc4120e20959665c85bd25804e3d0e1585abb5b07ec7c56c953ba2102b5cdb8afe8686c6e9f8ded45b48c4b940b922355bf34e28712855a254cf75a9e67fda3e23da0baa928ad0de644415c99ecbebababe8760a98ca1032fce5565cd850f0004e8c2f3f26fc57df682f6f9583ae6d51ae31e146cbef89beca4a82d84637f800b5f2bee9f89a874cc77d73ad031a3595de31f99187de9f9745848a150e102fdbcba400cdcddb91137a9872af7044280f1d0a946a2959fe9867b5cd12e48f0ba9ef02fc31aa01f99ab318a39d5c4a59948590e4ca9612fd7c85485667aaa3f4fc21fca999de9518e1b60c0c10d93c334e20b0f4053181d07985404854aa11b7e9b6dcfd9b41bbafc7be40b3ad194d029b75a0b08c1eda23ccd509b750e7466fb29df09db370ee61797002d00b30d17510feb87c6dc12d2cd56d5efd153bdff0af430afb56985b85de829ae3b6bae47820bafd67185f143f65150e67c28d4625b5311ffd26d53375d2135d27f602143bb3e33cd3e38e056b6ce97a70b9f5c984cfcad11efe1ca6b602a5807309a323785dc491a56b0b14b802a995f1b4d4080e7a64fa0866c0a5fa36fb5ac7d6b24f8b5a742fe500be2dc96674e826b8783e5e692b2d0c5e7b90724c71b823424a7bed26eb9ad088656f72bddbed0d612a39bf949a18f6f217863bcfb27a669e6885b6ea5e62c1898c2b3d688f73247a74ca1a328e3825f76f3bb80fa97c1897f21ddf483967db2f975070aa3d38e22cf72e638751ad810c6e8f60f08049079339f0d71f383582cf7fe0b43ca43bef3b726b97fa69b4c0411d2ad9a087170c411dbd7cf71ffc9844c4d12c1bf38ccedc4619ec7c1ad41a368bfd0a2f99049cb448c0b5c29c7ce0f7bf958900052f4816ff31d7fe6eb2ca8142ebcff9dff860c56eaeeedf89e5efc6553e7a793eaa72430422d1ac5e9005cdf5ab2b654caad56ac4802bfdfb08e39dddf90e5eb040fcc5ef927cd4b809d6b7a76164c2aab84b651cf8e234c313d4efb6a1fca027b493f43e5b8d0c0a1330d185e0deae495069f604eb8edb66910d88baee181354a2a39d006cc0167ebda8b961a8d2df23e87cd94b90bd37ea14e80655626e1931cf2cbbd5983db0f66cea7c0e38f0ead245d9ad9fa06464569f4e22ea82973ddf38ee2694c86552cb3484bf883fd55fe00bac26880ec4c1d860c85fb976e14afcee7826d483fe53efc18c5d0ce0458cd2edf2b7f3d108476b3575ff5f79af167898b9ef3201167776c68649bb8c3425fb414b8aa6008d09ce60d65fc986db918f47b45cd3a7114d5e45f15dc6302a6f24e87eb5ffd4ec3252df373f0f4eedef680b20d21162605a240aa147cf0ef9f2fdf5dad38d99fdeffb71f26162f39eb81a9abab8368ea7b604b60f41823f9ada52463d1e5e6973a60996967707abb0be6809b763a62b501fc68d32a596855bd7f39046aab4dda6382a277e54aa0dbde3ca3556ee867fcd507eff8bc112906fef35e6dbcab491904e02b098db3591789dac59e9d09a1f132399ed05a19040d3ec1e1b3884dde6eaafa9f7199cfdd563f4a2f45a166118c9f7e6394904223d601f658ded7594c9f2f637bc3129a75fa42fc90aac4e8e31b080134d09fc9fdd149f0a2d213b6e9cb3cbf6ccbf42acd4b6e79ac1e75ec1b234325007fa693aebdd31612eeebd64076794d94baca1c94fe3ffaab7083261f583affbacc2ed2a492c15d67f062dd975d30210c3d82ccd4e376308b43f80524e82275d01769149aa7c04a634a0aab4a16e886a6bfacf5c49ba4ea77bd7d8245492d00c5ae2fd67112c0d0d84d3d31b064fc3ddfb01a1e545198dd23ad1b994466eed8054e05d0fb6c631fecef55d8345592e4597d8c14a11d4346b89bb3e7a9c1d0dee5d6786ce0e4b8d44934d55ba5eb133b864d45ddf3b0d5df84fe44ee834f544cbc80abeca9bfe437c4578baf63d659c26b25dadb444940d4bd45075a9ba6df8b7fc9ba3dd2edc5fea4e56f2d8b99dfafe5a93e42706889c5a238c144d2a4ee3105ea33fe8d2963fc2299255a84a83bb0180e1e8ff79544097f93e607b5dd4f0948e1344f4416f26d6841a114d8985dd936b5bffee10e8a177385ab30e511f58e2ede9e4744089ace76c276b42dbe85252f63bc471396fd43675add0eb662d45ec24c555287cc8be7cc47fcbea19491edb2a4b1abededbec294c5fe9c3e14066be6003fcedf01e543bda0f501878d54f51f10b48e8f354eb38c3636f6c423002c04782f56028e867d4f4e161d08de89cf61b57c917935394bd0cb81a72630f3fc3ea3f08bfd68e2ca4a02c4ba3093a1fb9594bc7c2f04a9269ae44ed51450aa91bc425e2ffdeb7744e852d01654541fbe68b133b2baf40f0b5d16fd927e7227fe6716546c4fb340a73ebd04d862468776e4faaa52f75e65194d9656e223ef576a3b3d968f9d3812dec90a6bccf15b056fa884c9cb46d9d6f025ef5fb5d98622dd490d20b4c9dea395d97215edb7698c886ef34fae7d539190a8c742f484e4e27e860537843c5fdde7ffc78c39dbc970699c1dae41882d8ec9e8bd6ef14b4a6cacaa1b0a0f8e2123b074e48f545b9cf5a71d7fbb1fa85edc305a7346a9619f4891b587fecbfe97cdcdd6ff4ab6f3cd1536d97a879e5e90f821073adcb63538e2054285d412b8602357502c3cd81f868866b4f1e3a3a1adbeffc718e35a9d817d3d459f6abf602b1f060861fdb863a84e4fd26fdf610a580f69df8b866e3a3fcbe52ca863ffaabeb657b547ef6d100f19707cea2a44c8fa83eaf10ad3d4a9896802fac61d51a96b2da9032d0d545bd56d319150df0d6f245fed7a2167b9b4d24d59baaaf9d8f66c1a22499a0a42e1b2697c2c221b5f3136de5160580f3873681bd4869e4cd2d41500924fc8abbe8aa42f3b5a946eaa4e77539def6677f229967147cc7fbd3ee6da1d3f5abdbeced5a2ff4c99176f95c2d616925c408d3e0a9c87fc739612db58351be4330bb0279c35ab634fbb16ae94fd530cbba40d15b292cafc1a2ffa3bd4d2ff6db7618a658a3277b54e77281996f2f5623042741ee1bf2c5cd5d983704ac5681a56ac2efc738fa974910c25487edb4a0664dfcf3edd2bf6d70a9e5bcc40d8dbe5dea091d2548b6046c3355a358abefd3fadf8e1695a47e6678828a9ac76e5116b6d76af6960d22bead1f75746c6bb5031a809f6d304f097d2805c5c2ea0c895f6077baefdcbc6b711d3558973a36ba6be5e03e7e4f68a65a09f0a54dff92c8df06070db07214596a38b7d1b1d85fa052382370b0756163f6ad80474ffbdebea3c94148a2381034226a076f32450ea96f38636b69fd0e4d812e846af8649fcbc0886bc48bc9a2bdf86227ac4165697c5edc1397ea76d19891289313549e751120bccc92ee9d698dfe556b23e6774c273c7b2f60fe90ea6332a4c6ef593e5e8f44027fd720c52dc99c6feb58330feee859b8ed2bdf5df59c8836d4517b6b1e9820b259d947e367b162ad00fe944435cc64057b712baacaa24df596a689a783e8f8647a71d214ff500f3f667d0588399662eb88d082beeb925336ce22358c46799a042e55014739af52e706ecc109f715c9e1fa54dc54bc99dcd646c52171a7fb73e3e2fe6c4aacd4c03609e375a778a065fe866f2f6339b499a5123b8d1ab8740fb92bc1f74a9dad6be2fa2056cffedce54c1439e02db2e8ee6374c29eeceda1bb6d6f8fa89668300a1d9471a058d5df83225ca78f892f9a6090a775c343a53d5427d2e2c6378a7be69536282d8660370a04a456c0923e35cc66df5713e03c1618990f993f9310a4694f347c0291dd781d2bad8cab1d09d3fa021400c7925c9b2db7bd0f95871e9ebacd1cc231b19b2de2cee5f275ffb56c3a7fdc1c51331b09583cbc91f341b77f0b5b290febdc8f5fb262bfcbe50c0a88195f17aa7632c56ac363b893adf2050b32d6a85c050b9c3d16920d207e97402ef7406ddf9e5ebed40baad1d0d96bc67b78b9939f02b46ba389d7f7c52513e5f0c5a803a0a9c066ff59e52ebb3e09d68cca706c97236a731e0c7ca439f23a950ba164649237fa833a3b40690ae14d5810b9da5bb42055763b18ef35f6668618af5107db5e599bf401b40c3ae27e19d27db3f57923155a9f1fb70f3c061f6d7d8275bde7f564081f23a4a80a74575d96815e3426d2ca11e9fd3088f442e17b2a5e7381e7d91ddaaec2fe7b3b6789c6a6513e445006b54c37a9929b8923ca5bd77f3b31f233d09fa9c784f9805a80208377774edc17383a5f5636b5f6f1a7408e256821a110cf0255ff6cdb50c70b62fe04b54ecdb3e2bf38d1e4b1c38b7ed2a067eef8316aead09dbf754d93e9a12a7f0bd8c219f81e30e63ea58313bb38c75b8ebe3332b558ccbfed0ecf5e8a27c72c46b80ef20c78b635231eb978ccfdf846ca96a1fbb5ec348af9cdff4fc8cc9f10dfba1812ace8243e8746fe21f21dd28e658fcc03ea3355f44ea23f801fe3d1437c0cb25062c9734e11ccfc0e0c696c7db41a064b65e5c562457773e1afc955af69de9593e0236e562687a818285c009f0b5027145d86758e0bb6586325d7011475dcff64a34945f198a297a97b83a8e9754711bbbf2733d4f2c2f0f57f928e36f5918b103831cab558dc7dc3f02e7ca564734f9d1e0f70f98aca240e7a063968c7b0aeb095a384bc9407c40a0e629fdbaff151564202b7deb447003933fe1c80da69ac61c472a697b8df9d0125e93d1730810d37d662e04913b96072039050a0ad36814af77129343f16690a63113985b52c20c2d02991515f8b76be50dff8d115fdc2c91d232b07a193729484f26c7efe760f06af154149cc12bf438e6610085b0d43b2408b50f5e6b0f6ea202bb7491194a2322c3a69b1a9af42b1c1f2dcd8c51f1859f63614ae507dae8ac919c16a3d7db4c33d1907e99d3406039073b9af2f2b5925fa1b503a590c2c70e45b78b44d0805f29b82a92ef4cce43c5d3da7d3d0b1bc9097fc788e2c324db1811bbe6a9abc7b2e72df3e6c52ea62458afecb6e8f886ae26ae8fdc28e55f076b8e114f5d13bf06ea717996559aed87ab91d3d772e3c6eb9033bdca1bdf96c6dbd2ee9c442f9db2056e1c73baa8bd378be395c18925ba78b5ad7ea9d4ba030b57019521666060e5629cee180171359a3e9c9f89baed61f2557aad80b28a816aae1272cbed3245ef3af44fb20f49f7cb48573d46ebb6f158292446fe19c563289b4231fc31dba4b219fc8f128812c8746c97110d6856e49e8e861b7e5296c1e6c6c28ae72c06de77564ae70f0eaf3ab5130def90805e58e6353451862b59738b1015141fc651130ec91213c90bd266cc820a25ca4335a41600f10c260b03a17bba29172ae66b4ad2db7608e40a4f2a40a998a28ab7dc4d5c31339c1f1e67dabe83ca7e44d41554efbe2d41602240e5073a563e5e96e672da2423e7fc7c53579f43e1ca5f4e301124217e467971cccbe18d3186f1b0c4165a85665a2c5cd99d060d6c8de64109dccdba67cf8f1ad1be81c05375f237104205e4db9a50d24a68e6e41a81c8e49f0f6528f47b08750e17bdae6c0182149980f1bf00bdc6e8ac869045ddf56553e72a710e01304a5a9af9b998f9650fe1d2f7cc7e6bbab5f44c686eff02e6d27bbdff7ef09adafc5e053deeb9deb5b3ffb58b1167874c5064256e089288a9ec1301f6828cb090681b07a590f22c72b4f2a8f86187d61f64a4d2b3f40cf63aaecd446dce6d5b515ba29d7a25e0a1db413eaec66a66b8521020f83f2f925a3dfefe5bd59c1f4a971cff7eaa70d25a8d08d2e4e56b9721fd9ba37208c033d2ea57149db8daeedf5448d2bac28e8e946fb2558b6dc4296cb889fb8041e8475338cc309c8d5ce9cb8c237fb61a5f0434657d883f8e6ffa989cbdad826373e91bcfb9f9e84e080d6b2482c077cc6ebafc47b5975ac4ddef25c15eb318405abd67a9eca41edefe36712f818be18d1dcd5bebec27af9a5a69f66195f0614617047cbb447dcc60acff62ff4747f1f50882a2091446542fb72058efa8d4acb182e4efbe8f7bd14d255d3cc37dee7dcf6c1c1af69d9d8754849f58c72659680f3747255ad82ea84856cd8ea3c6d772a0037444a7fd21c984fb1064bc850ced161b29326047f8afd52c8a6a75a16f6f7e5bf9113e24017a31a396617568d94bdb7025180b52ecea4bf6e8fae969ca6a84900cdb337fd07ba6d30f746994d7fb19cf16c8ab11ca61e8447bd36566e410efcb668ffad7eb2bccde34b243934411e0a5658d79266cd276a51dd1b9c007ff8e7fdbec7f4cf529a3dabf5a71c4c5e768ec0d05c1e1172230b6af0e52aa3f3bd2c13a6a9087d1e244fdc1a7cda34b45e29ea0c4f6d2d4a966e018ebaaf41b927de2902152dc47885c53f6806cb1430b4c1aba7d7c7c80522f23d0bc99949537d46106e6e5387b32babfd60affe8699669a45ddff0d1031a21f000cda25be629a99181d90cc2d29b2115732ded8f0a56968da0c4511f85e68888ab000100bb20211c6e2568844ad43a861c2022a98e0ade247afc48813959adca5cc750d48546d9608f0fbc8c114de08314a963ecc504198bd99acf493946b10467e763cbce4100767863e8b15a4afe1c8d62a7bf8b6415734b6b3ba4c7710a40fc96c6743111a813af003fffd5a64f6e59c2da7952bce0a792f767281fec2333ca274c1a8e2bd2965fc606febc1ffe423ef3ebf26b1d978157d8cc20816ab8a5e9a2567efc898a25c19aadf2b31030d7a8e5bc3d6aa3b92e79216ee18bf2134075bbf8e951a1c76f3ac68c16f3bd22966686d057a641f1e5fd1d54af5e7426eb7e878036a9669bfb22d1ce4296c19830e0930afd676bf8579bf7b7f10c784c9695a3e95bb888021a9029ee31b3f295fbf40999a7877d35f9377bcfa5b11083b259cb9ad18bf08cf55abd594a7a2d57830857e6dc702c092fd53514bc4dc8fe86ccb9e96f3d11bf0de81f48bcf08f5ac973342ec5953c771c0218deee5115d5ea2f4807f3531013511ada67765715d178d61b6a953d0bab029cd6c48fc3702e17e8e56cd76f25f95501fc2ed204b2835da3237898fd9a9635b1c437e3a8ca7f144dcc1cffef3d409e54558fe38444e305e55af6380ce07f1e1f20fc0b0e9af4567d05b0b82a08ecdb90252af08dbb6a00d3c1a34d9b4aea97e2e944cd0ecda898f90aed426d4ec874cf8f06d1be85c093c9b2a2cd4bf354f87c35f322f43052f37fb38b6535a895c959ef01bb7f5e671e066d37e77678f7f89b7f952eca1b890f0dbe5e8622ad3890dbe6ec68bf187d949a8eb3d311fad693f5d68b87547e351caaf7161c35968dfa79592a21258090d1f565f7b496aed90b47b3619dd768f4f5c9df99f1235f021c97c0f8f1924fa976ffff7bd997c23701915383aef85caa203bb8491acc75c57e6121c99d1bafce614ccbcc676d0d7716617850961e6ead990db4005d86ef5b1299d4db746b6aa12c845a0e0e989c849c630fea27a1684c74fb8737ff00fece03770aa699f9ee9512deb728f655d96e83b4814a07109e453eab896589d69dfded5d5ef58947c22c6ba76d0717927cbf257a5766a42cf703d3e681d0d7e9e03a93cfc91754407b298f7690e68dc884a886d9cf69de9a6984f01fe849f8280f5dd6756d1cb6a68548a81f1b57ae4cd29dffbd71685793b73b171971bf82a89e5bf5a48850cd1ba6ded1a62cdd5b9c2f8384f827b6c7acb32631263bf97e042aa0c747b68aab61aeaaf7c859836527777de1393b39a260385f1f91b6898aa300cfe0ab7757f38b4afabf17b87b92ad2f19679a69b4f36fa82a3aa64ff1ae701fe1301f50e187c6d03ad61b32bac5c3539670ae6386c66cdac19db65ec2cf8d5150fe25aeb27e2a340286e73d322feeae25190f1c349a736ff8c65802d233fb21da63a1b9f3c872c5e97ba8da1018502aab95d41f827acfad0272a312c9920ded0adf2db8bd96e9a88578444a04001b1f1bd9f74de7786b70a67db72e6c6a6ac1d839efa979afb4c6f4a53d383edc0e7068830c64b6c853bbbc6a0f4de564c84048072c8f70ac561b78ee61c471c3b37dc39ae77f9080e5ebf1e2b3548212fb25e202ad8f611f2ea9e006c59d0727584fcadabb0897fc0de0f48595c5d6a6d33b94fd3c75d9837aea5ef3d6a2b88bba996c64487ee235841af181ee8e3e6c4fec152c96a08dfc762ce33058fcee57124a7e4794a1fb6b9bf041b1ea95c6802c4bc2365254ddb8cb82121cea0d45acde982f0573d8956811a936b9dab3a7c23c24a3d9f1316c7e772b6c113fc2361e5cbc96c2094bdf940a5975d6ae7a3141d08690b3a2462c686d9bf687e7bedc07602ed2c95ea33c6561c8fda2ba81cc93d2f5cdf45cebef70fee4dcdd87aa1dd6397c1311de9f7a81b9bfe558fde7f82f72a6843d9168138d44d1dde4ec6bd7ff7e4d915ec3017a49221ed7d55c26cf22b3d5865953dd20bf7d40a2940330fd1b62625378ba4f3541a1728e13b50992aa6553b96f958863beeed290980c06beb4088a011b0ccc5f9a4d50fd662b116b9b6fee8c4b8e6c7450125b9e959cc80ba2d651d3985e1a4161cb78d3b5bd2c7f61d87ef7f39e7010a4bb744ade28b8a861dc9b56be9c27e472e52f136c8ab21ce15c0a9f11379df43b7da4c8947f3670e1fd77635fbf6fda431457938407adbe06cc4d18b08fe6ed5fdebaffaa4d82297ed7606109092e5e5a3e125cc5fde5a8dd2d5eb8157e37d5dc64400b909541dd2543b7ef28a0af89011bc12a747f87b8c4880af88aa9107c6aae6e8c4a869e32d6e4aaeb8cefea976684eb97670b1187922415ab775b84eee4b3ec8235d67b3ede627661d693ed9b2c3aa4e2a72545a0582843d896456d3134388bc032b3b11a01a6c9a4b51d4d87bcfa142201541c4d74ad6febeb10e3fe23eea1bbf267f910198fd287af325551ff1957333cc3cb5e8259ff7daddd1edfa295ba73f1fc3894986116250d7bf0fd37c0b924d29b7958194fadc12afaa0e36e8dcc9ae14122baeadfd32766513cada6de2bb4413f463882ae58e3b9fb61b348d52ee24f1433b3eaf961003e8bc0c662168b135b2d514d6d610aefcc570084db409b1f60a345c04df01689990637b182a5597d621e74644f2234fbc30cb62f5d9afd153fd0aa893e974ca778e2ffcb3748e58ebb28f4df9190ad1de6fee2dc62172989560d2d781b66174b9d719e79e977a805cafe8009db6cbeee7ea8ceecea75f8f3b814dc4ac07b1e6b46c57eef3163f9f2c65190e7cf0f65fdc17bbadf1cf3400a91f8be13ebf24b1a096b0f5d30b9fc672058c0ed9fa7a2321a9c78eeb8771384f588a6ec1e5301242a242c686fc958dc2b78463f206b3d628b7f7f85ac34e222cd781dc520c42e9632cc20e3e221258eaf6f97c8ca5c3f83f62e42f485714ea1a7b36db52d65ebd8df6df8727791b86126b0cb5380e8f5861640a0d3b27caa13316bbe91bac0f5b6edede05864bf0473607078bb711d072fdfdc11604fbdece8a78882238b74e0d79ebec25ba9da62ce71d4f7ae35a767e6bf1c339ba78cbad5c3141a37596c956ac3ea33753ea027d75993b8743fc6c4aa0fc3ffc85bb0eebd69a8cfb5d8523dc294dbda6d9913b2bd1ce6a6d11408e4d20400bb1107a97c00b320aec6f4c393a354f41952a2c14dee54d21e986ef4731fc06a99ea34a61701f778849c4bb33c83bdbbe6ec25f07da592b9842ac414a85fff67f5f7e17d8d439bd9fca241d6d5cadf2d33e1a4150e3d69c2c14b1463746aa41814d8d2eba22b3f4f0be1dd5bd365a3ddbed08d280278a6f5a147d5f394487683dffbf66fe0a2a7c1c29dfadd8cfe09ce58231a2a747c727a57d545286f8da6284dbdbddde063c9c935a8c782844cc2b5d0edeb43bbc4018a2135b2ad617d3771d2e0c784f73588425d5d9deafee10e8d7630ca680212e5b9dad7f75869602d47ec836aa24ca1c3ff345fb4978e605ea87ff5f4f6f9e9afcca4af8fbeb1267d20b154825e9f7d1a22136490a66de725c601d8f737b8b3d87db031706837e987ed2e67d48d7f6b60d4ea95bf58a689243ce1b9a4373a92e96a4e36396d0d9be57e819c66d6ca328f245648c1d048137c29159a432c6012ce7a58cef348c49f09feb06482992ebf81fbf6b235d9e83efa37c54b0710700d8371503386e9d77ea50805ac7ad64ae7ac3db8d4c9298cec2631ceabed77e1f59e60164bcfab150b6b7ec97ba5a553173a2f5dae17162db0a233597303ea4067f36f9dc865699865f2e2f87e046a7010b7ced0fbb75e8248b7ccd03ce1b6c820f308747b09682fc0fbe7ad1bcb25fe86574390e1f3c1cb5108b717a2197cf4174219bdb8d482cbadf3405d49ad392d7aebf7397f68fca8e79401ae3f4f9b68405fcf075729a660f66fc76949d3f88d816b5f6f595fcbceede915cf4a2d1943845462c7c79b3fa9237fde8f029a19662d7ef92a5966c3aad68687c963f6ba0ac33cf62f2c00962e8019a751111f1d2a5a322d2f4b03643b36748cf2d7cb4161aded97b2d29692ba37ffbb01f369c9aeef67f7bf40bbc8f3193cdf264f5a1c777abd6f4a3d5d57c5f5c55910d11a6f3ee315ba9c0414f1fd3a743d5e1237e2bf7d62477d09036bda2342c64bfe5418de3e35041f641cbef6b50fa9070c525fda2b2b5e8e0286a8df4e22380c97d473d30e86c156b1beb826eb868b43e46fae5d05e29b7822569e469770cccbb513c3d903a9dfb497bf23bb701c0047e0e84c1df5bf8f4560af390f93d5531b6ce09c548a0227f657b5a2be8cbec1eff51d49b66004fc97b4e4ded965587833ca7e6a9681a76e1b7c1adc15b7e28704644d27088f14f73f5508ba45bcab0334a8c9583a812001e40e50261977eb2f005a2004e0ded1c5fdcf8cfc95f3dec40681928a51489a6e07da90b01d0d3b93d048c8441920f6a45e220ac053695be6959e8d9b459076b39be3d8431d54cfdf0e8735c19c7a4c3262192539b4ddfef545fae224bb6e1213dd5fa255c499c71659d031b2cf59efebee0e22ff3ee64c1ffad462e288148b7fd38d760d923e96f9d1ed0eae8952188bb4dfcfb73b4932eaed9ecafe67a1c4b5b30bf7318feee0c80bbebdb14af9087f0e39ed89a0f7f62866fe5b3d8d6ba22f6ae12df5010eb70c2c14742c5496492a59120dcc43344677a2b7bd88c4db5d849b68969a54f766061b9130e88a47df58baa4d1727b2508b2cc53334911e7c7a01873ad1d5f2d6c74d4ec5797814c1e2c1fd2345e4d204a9bdebfe032a3470d689eecf2bca5c6fb3617b6e62dc7e1e97566bb736fb3f133cd109e8be6a8aa5ec9b622d0ca2d16915c08e8ba0fdf2d581ca534c922a9624b2fa4c511470a60c1d314616005ae10d7ea9b8a021bc333b0221a623981827d3dcd471b02d8a49410e512acb47d1caa3647a128d7fc730aa312bd9e2799f6fe2f6d669f6a4f16209cd235a8d9008985063a76f7f7d77152f99b1ed7f7319a7022a86c00931ae8be92cb53fd555c31898e63558c0a8e686fd2218a69535ecce293b991f4c727ff6dae18a946f25c86bf5414cfbc2dff89a36049b3ecf2b47c6112727f4996defc34900c4d0048825ff0ffe77c0a3224dd45a79d2edf2202b42266963158b357829891fe39690039e7e1610bf57af9d7eee70afe2106b11681d8e48d6858652151c74f53592ff41dd840c4fdabce9e72e9f868f7f335a6e2e5ee3a1ae27c94a4a518f635cd935720d57924e749be25320fbe92cf6d1e3d21a57ff7d43d975b949ee8cdfab7ad30f480e8455fc7676b2f7875f145142078287dd7b725543b57ef3f4bf2d53367fb3871ba97fc2200395bc4564d4dd049071ff05a97e9ccf9cb391aeaaa3274f04e2b11ec8cc802f56950a5125fdeeb07448f629f812191e3b1cc963e11c9535da4d08e5aeb6d47c2496044606e50fe1a3ed6949ad87bd6f4c0ede13cb1e968f86383f84f66fd0b7d0a452e4ddd7d091195f6ae52a7f00ff4f6a638e6056c98d6caff53a2d273a52f384a79bcbb02b8a8316a5176644c668197d8af2040a8a9b6fcd5e9e48fc8f5a8c59b61cde7bddd4663f0793dcc066e5524454adca9ce0880ddc9e120dedd39d3f86199208245087502a1e1d185d80604acdc464d804a336b22df18b2bca4afc63232ca52bb70fcbf005bb36fe7a86eb169c2cfb201b06bb16006067dafce2e7364460d88962125c2d9b245b7570a199e1915477d96bbc01643ff351b7c649f37bf5cdcaac591239915d9fcded5f6ca082b2b6a3469429f813cd6697d84ddec5acc3167165547814e2860f7828bf447441c37840cc71167477117b9261fb63f715c66f1f125c073726b887b4233f27fe24e2485240b6d115c4f0c2e0145bdebbfafeff3facc68f5c7886592e45162587d207ba1954fae18f646015b049d4dc23c9c30c8becaf7b19555c64d0f6da885b4f9ce783356e215a6112a3001ade10bcfbb365b61375400565e5edca63fae32b70cf51d9355c578e0cabbd5c3353f5b429e9ed2a6bab59bb8680a14dc36613483f1053717ba069541feacb20817d2e427f2188c2893bc9952a1d962110d911c563612d2c8aefecf472064de65934930f5e3c07d1e823f0a12c5ee477a9b97eb9ff3aa440e13bc0b900672e233abcecda9c34e731527fee84096ff50197ae269da2c8a57dbdd1cb8dff15397fe63ee88e319e849db3fbac537cdbacfb0989b8e382b26eb8e0dcf4b86be5f380cb239c13464a5b65d128019cc01ee84a2e564f8f98cc66085e1f1f30b200be6c52582652b66eaed28f067102e234cde8d6250d6a3a40dcd8fca2f3b4df591658de1e0ddab81e88059c86afff2c3b9e78edf6ba2d2bac65d156baa9732480388f1dea9036c69ed6da3bcaa29805e00b6105855b96c2b0ee30794fbd2d628cb9a6bfdafb278ac3b076cac3ac32ad78de97923f2b7acf6df12d7708e558dfe9e8fcfba0e9064bf393203a1fec5d3d46c64b6dc33abe33ec71bcc8e6e7c6de586eaaaff27bebd1e360995870b417a11183c3e1b30fcb33c140e42bf62847a2f724fd84586f3a677b6febb52f1917c46d9312d0ef4de0a6b69b0bc9fb502790fef199f784303fe54410b2120a83a5e2c89159bbcd2a8c1219768ed38404c05b203e2b0aeca7958d855c98c01de5d7a4a31de283b2d45a98fd758dbbc7e0effbe322995c0db7db1b5ee853b1be3adc74030507cb7786aa20d79b523be7559e529e87533d64b75ed7fc2be35e106a70cb523ffbbe6cc69cc00bc83c39757e5908905f3256aabdb708671adf4cf4253eed6fb295fab731cf3d32e8c96e0b2c583f58b155d1f3bceb84a877f7928f53d1583df32d9aca1798ba3d6ee0bfef9e1b3d6db107a4659b2d089555f583a2a3a17a2006215d1907442e8eea120c273adfc43190ab3cd4ae74575bf1110bbfe56ea50e7d791de33e5f9bef43ef252a67fb08ff3c48e526487285ed9908883eb3e80cdcefbfddd65f0bdbf23f143a183390a935f92a010ba6c8c9284059bea5bde8133cdf3e493c57e27608a461c9bc23c4a8f945cd2595175328e0324e4f18d161c7f446e1f8336ae11e681aff905f94a167bb95c2b1d9974861d52df9e6259dc9613c840411de0b6469d88f7e24be0fe0634e77afe5d54a4bfda11e53f6cb343c4d89c4b842a4a4dca3936064d4b0c407014b17278165a66360b2dacf7dcaae91f1549bb71f47c2b2a2af21790f7b3c37b2df602defb7f7b14d3f66b319d8fad85d35e246fdf67a99d7ba9e5fefb3056fb0e15568e4642fa4f07ace193e920c00e32c7b3796bcfff99684fd96dd66f3b9dd6ae4441f0858b97a07e03bd57b321dc759d26eed212a1c645d6d883a00b8d9dc88e7e223bf90145092ab7ce618294e7381d4b2184674b60afb78dc55941e4a74b45539377f53a46ecbe850a96cd1137a00ef5fd2601c2444814deee7ed69123f413949dbce139fa0c9486b9196ade19108e08927ffaf88f6f671116e3ffb61705efc1d8e0c04649077d67eca799107758c93ce770b674e8f09f56b7788d4461f19cab540d9d48ff2c4c726faaeb4c592504cee66044ae395b8caadbea590bf3037c45c75d7d43a07cacb1c591b063ae4335894b53f1c7d8b4620eb91606d7366338396546e7be3740adf21440f147ce86af90e205cad83ad3708a4edeb0a1b7adfebee34368f3b51d785c0c08b9bf8b7e505d152b6caf3b637329d0094b80a27de706afba061b08978174fb303063452560673532c56751c79d7d3061519ee28447ad25d1865a369a0ed872fd6d500bb7a2b3bac1e5b1939a2afa4ea0f3c7378c2ba5ad203d115933c50efc3a97ee330e78db7d1f6e08b51b2317b560e755b0e79cfcadf86bbffcde95e704f0508d3a55bd796ac924e1f38f52e4d672e6a6f03844ed698bdcfbe5a5a4a847fe51fb5774bf4777a0eb827504bf8a16a070acefe35d604009f27218a2f8a3413c290d0d8d5a7bfa4c1de91a83c608a59851175fe6536bc0001d57244f9c668ccea889c55856803b4d988b94d6b1f1678a1c8aa6db2aad1b658bcb35eabee08b2242fd7e1fec7f7befa09d9c4cec927fcf4d7972ee03c9f7fe29b6bf047eb4419fc6998b246b70024b11c49df5248d9e7ca431f2b7a1438011380bc4c47e8b3c5d4feca602a48045f833ec26ebf3fd4ca84a2e30c8ef1e88d83d796233c27e323e235cfc24a1d5206894cac12efc7abc1a860d57c1b67ec0ed84450060c5dc29ef19ac4d5757f425e5a61d74e39dd5310cc977a883398c24eb0c9644214ac45d9119c8654ea855e7a1e6590ba6d8bd9d90aa4515e4ffd37b39ffbd256f97c193bc75ab3b77489f6a3518461ce1e48514097eea2fff9bd5e3444b1d056aaea8fe107657272e16af1438c4029962297d8ac52080ee457775de037df286388aacb8f3fafb0a69b0d03fff28379d16f6ba9881d6fbf51cd0011c71cad7248ced4f1f0cba4da5d2417911620b8ca9311693969f898e9303a644ca017fc663642b1e9092eef113da117d15fd25a9cdf861fedfa923f4fec0971851692f1509d84cb6e387c148aee0bbd526c9c13e9f59790fcb7c1b29381fa3821458610a1fe7f3235d6e874ad3871fd733222bd064960a609100ed1807836640c182889c840d87397fe71be492f144bd93682e05ee73c9b01fc71e6dfaa2e5cc04b8638f7d74a04c9c25060cf969e64226637449e717b7ea8aadc69e4437bcc281306f0bfc1dc22fe7be2763e3a8081d7b9dc5268d78bd8f53145da70e06f7dd4f42866400c7feafb824044932fa741035a618cd7fabf6efd0a1b370d02cc54fbdcd78923c52b61cae2df0fb7c24e751aff986f4114ada419ad742852dd7ab24080e61b55e93eba344b0a4b9eecb50396eef41cb8d6430535cb51c1951e4064d5b2d6dd3fd19e06bbfe521b11161793a5879ef9bbf1f4a364d09faf6735f93522f59532b5e86eaa8adbfb0856ac676abf54c677f24c75e7f0a06a782a18e3145f05deb250d1fa85d5ded8b590c890bb36e13c1a54b42e194d8f9c62cd0325d7fc89bdef1c34a867cf97bf507b9b394b13c1f9d03e5cd8f2b112f3784749af0d7d96891b01485addfc43f5fdbd5fb6dd95d03b951907a691491debfcb95b5cdbd3b045363f521c1e6de13b7a33b39789c4c4e3a9a3e76800703f341a887a8b860a3f7e921e8f081a65f579707536b6f23a524684d0c4d024b47ba8f4af3d6e766f9b21339a8b8b945d4fcefbe5a79c36aa47254c4b4b8b96bfe8fa2cabecc8d40cfa4636ef66984169001e4a902989b4c20235e6de60dfe57d09fe33370ff7cbe41da3e855dc193e13c5d002bdba54cda161bce76fd6d3c5cd979a6c7dd54bde3fa047d0e86603bc6335ed89fd0124005b41843bc59530da4cd41a36a0b42af163185ce8b8eddc97da0571dcfe90f324711e9a79bdfe88012e006008571a7e7bbd386e85c10d9921aea681a4b08a34f2b3403fe2ef203f8646190a0aadc1f829a573422e1dd7bc6a82cad19fb6f2d37a3787948824befa71c191fc848fccb829032a1edbb31731b4f2f53771b5316ed5fa50ed395252c86ee92f096aab2ff33c799a55d3b09ec5720d2fe49d0a1b95b17b09efa337c7b3bb84b496112eae25aac5a6b123b2380d346c8b296cac0bd4a3db4f4074a806661b249010d44b73b8c1dd28e5f9ee5723abf4a06afbd9e12062c713a89b98d99fafabe6747ea15ff2bda4d295369b233f89fc14cb222c01bbd4e3aeb9d46237402a9fbce652ba518230afc26a372810ebfb21b5792fbf6868b193a28adc7243bfd618c19bfd72ac3c64483514a65f7f485007408546f452c2aa79cea1bff62237ddefb6b6006b0defac711cd326312c17d9f05b831ed79a5993b2819d6cacacd582f700afa36c69046df33f40d791e1586d1f5d5dae84b65e5874c08c1f5224c37a690793bb00a3ddd9811830f14f3249ac38886bd3f2cb4981392ee0ab5a00de5f3b2b5f20a58e9734b07641053c0841c8b4ae1f5151ada4a64526cf8000ac927556b0bb5a94a42ce59bfd031ccde3ed619544db3d90d4ed7d9dd0f46a1ceae2830a7e1d87fb15a589266b31efb14127962bff608141de7d5dc6a2389edbff276d6ae42b9441d42b7bfa74f6d7d17d5c6a59cb3700163626c2068ab92237a329c774c8293f477d988cadd0738bc1360e6780dde796e5d50ada6c09793995eab55c29458b117098567004b10c3e58f2e2c3977a54d49e95ba117b57a59733a014371155babfd6977dcc0c9d78789c5f827f10e28312aa154b5fd1f010c24213dd203d8aed798e9fad01a121badc2fca534fc2664b164c47feaa13ad581668a9f8481732c2d4e1408c6598e7fcf49040643bb690d1e201e201011ac1b2733d6e78f169951fc2e6951e7d341ff448f878c87c856361603a73ab0b2dfb1ed44de63e14bd04f7de280912bda50ad7e53fac82de4596edc95b8c099421ffa5436aaf3324421c8d1f57cf028b0d45f8ccbe4c4aa23e0f357dcdf448b5f39f15f059ce92f03c9b8fd4e3fd8c520cc5f00496bc69ea087b54aa6b7839be5dd241ec14f4a2a1807b99f1faa96ea578819b1618c12bf72564ad1b2b5900cb03c27c48bd2112df0c9743d30852dc569ef8fd49981efabe059d84889d69c6302f48b0b1dbcdda9dfdd557a08f7bba8c9285942863eda8a21bc6b0e4bd378b5f4864a7b7eaf10f5fb436f0b58ea9aed4968d17f345538cf29fd6b1763d2dad5901fc777fc132da98fb14af8db0aa24cc018fae69e3e07a340610ad44ca5797d4d08fd75fbabb431963396c7dc52dae496e2272297ff4f7f17e633ffab9a45b6f9a7aff78f5606526bdb0f532c53a406c775667e02f69e11d2920b9a8e0a37ba49ce424c52e3666f1bddff060da3bd385b115ddcb5fe5c98c1e2dbb01e5ca6c44dd8605645d4f0da88b89656181e3fe2779311498354ddb38a25fc94a191d964bf7bab6a518da31c8844d21a9da3d3e6af1f9d79170fad416afd67589dc47aa76a3eb4cf6200b6c149932448c0b2d24571a33218e90ffba8c266cf3ba146f92de7308afd447c34fc7110b9abda2232ec5bdcedf0835456c51f5a688d46114ec19a1302487f3cf00c999ac4a7130c5e0caaf8d806e0fdb25e3c3d65eebf4a4f3ef37dfffc1f1cf8c643f024ef49007f0d51f7fc35052905fe01060952c75d7e2e68f3db831c2a957edc8b0d716f37d71969b001d8f9b13fe74cae72831d317688689d9701d084bd3ea9439713fed65f48a6cff4a6ddc5635b2ccc019a65e2279dff914df63ac04d7f88f586254cb30ecff5d6bc59fd423978929571d8a4efd78dcf1fa4078083d8dd76f49c748e29fa2c082862887d1149f9bb22f0ccfc1534c396824b644761d3f8259b666236bb605735f32f4f1fd2b51463f2075305507e230587bc32b51c0f9b95efb6cb54a058f7acbe0db17b279c808dddaa76b4a8f7e1b7b41962d12af97c8cc2fed875711ffd90c930dd39327fb75c0683b7a60d48c10498b118cbc9e8b6db9caea760d65fea4864fd25f80a6455af64268cfee276ec5a76de426b2996c6fe21a5810875fb48f6e6b40bb170ef5d3fcd578b81252a93cbecec08ffdc637f6e5483b71ef790dc7757f373c0bfafb8150f036ba16161d313eecc0851673fd42351ef8d706a6efa9ecf5b049c726293c2ec55aa87596efa5c381db183a114fb94283a1c219acaf0316f69fc3c860f0226825d6b0a41ce7407da3df598310b2d76530d15309e405eabe96e27f10450d74a0236fa1566cb3f44d0267962d925f6b5501d919dd22b32c1a1975c2153095ece72f206d4b2f4413630f02c0487042cd15a1fe2b64a194e1297419c404c627fbd59e7740e2f9aa4ca933d3302651c2924e4f5dd549430586557f1abb445c3f97919f617cd5ed85d14a9a98b7e6185e3c9e34dd73cda62e2bd0bfe0ef276c0b746770995702ef4cf83c254b41ead65b3345e50a98f6c01165857ad1d8e3c0c620bb795b6c178ee1c8bf3632c8166b2471fff58288bc0add12a5f091ad78fa35ef1c0ac0d9cd764c93c68d840e538bd8f58800fdedaed3e6eb47b41bb0e3852ab8ef93b40e4ede7e4caddebc5ffa2323a69724b6f94c3e24f823758f54426df0bbc0c2820115bf109ad463ef13630963c183313378c6013751dcb613f72de8892fd7becbfe15847a7bab3a0baa4f337f08b57fc73752e2be57e3cd2a8c0244639fb6528a23b7ab2a6938edde1db944e92febb461c739ab3d547af65d3d3cd8bfbb9dce38f379ff73906a4da6f649ba5528212edaf27ef602e41405f959156ed12877c3932e4e9bc2f2bbd2a3dd69afd020e5b7b9de6a8866a632c300f85dbba2c4525f9c546db04f869c70c666d663c7dc449393468fc787c8c3af528a14bdc39d4f65afe8164601ffbb835a7796bfcfbbe29e79d570f15c2ddbd209487320720181d5359b9a383ccf647d4753cb9f4d38f7e522d21812c588e204fdbdf75ac4c5d605ea5ec854beddf822cadec4b3cc03441a426950ab3392b300e7689db35ee9bc1cee063ecbb2dfe215842ea20a9fa49b98bb9d477f1bf68ae4e7d223cfc5f2ec67b192232045a3df039821661acea7fc26116d7ff52de4faaba54cede789b353cb7827b9985bd311b63b19aa6c664714959774ca34aeebdcce21eefb3d15f93dea4378cfcbf1d47e6e49a5212b6a96bcdd0ad6f43601f19deb8ca617357febd1fc38b506594ebda58266a6b96629b98857d2cbbfb8f6afd721382eca06a857a27e108b4257c49b9372d6be72f57da066047d285a9106fa6f9cdff8c436f5e2bd5a3c0cdddb8e95eff3dbb36d9404237de19089a66ace5b09645f34fe70e4146dfe752d7cc24176461c9e8d138083f0c6c37ab8d68b0e17e3573d15909d8f2ffd177447aab308168f39c23d92db5be023d05e59fe4147f93a73ed106980c3e91027d9eef105d4d4269a69434f0942dab298489cfb544e5b886dd277ddbb1693a2bd61b19db9938a3b148401adc8b47b7a44b3d914f5b028cc167081bda25e299429b9eaf11d2ec55cd194c44c36dc8201a920036aef13ef7e2078c33652f75a5a61a13181a255c632997a7f458190db0bfa95279b32650dd38d5bb1866423c1e0156770137a35c40772a03c5e0eeff06f2c208c9260c44f39d2fd7dd3bc6c4be1b4afd577151a3d24f4030e5efb2fc648782e6c1531f1677a946a07029e43efb300f3500343f816faaed99d98bc99fdadf6f0909a7bc7ee261c9ac5b3422d7bdfc1f6b171db14d61b916b28d69e9e90a6d9e37d20d92aa07f5ff5c7213b2c4fc1b49cb539172e8da8b674d991ebe2d6b0a2c01d37947744f8c1610b48c56babf705b4d6b60d62d6f69e798ec2fe45ab10a7c31f993f47b7281a72a4168f93527a9b35c502beb579d47de4d6be6a28a2eb610a6fe74658fe0aa9c922a7b4337b0ed263ec27754d1c90bf252b944d221b1d6f5b2201c6a8a007cecada296084c4504da5382aa1f40738f0c9d574c6ead13bb33c7546f591473bbc0f66fe5d8fe0491571c32c5dd0f097e5fc305dc22c1590ff5aeb78deabfcc25b1fd7b033e1d0ed8c4129ceeeff7002f75aaee3cb19bff3b4225b378a9825fd1251a25392e82413fa6c14b624599fdc5ea1241e2998b93e4e167b1ae211804281a35ad9d8df42c76ce0da128389f60196cee6b7f06114a9ac82270ac566ab61f5d61b9740299e61c8bf301ec95edfe083f706374000ca99d0e8358f2f0e0527bc43b97559dd8a65f6b6feccae470fdec52b30764b2f526f32b9cc84b27855e22b4ef379d064fbf593d1002b887e910dd15967e12f65ffa0f575b8392afaa9e3652003d6d932e85a673ba4e0738a0fd6bcbbdd0fdcb1c7e7ee91bc6ce83bae6efa70fc6f426ff8ccb599aca71218e35c8e0538417f325aad6296b6161dae16c33fca2dc6c75faa1463216ea8b48c8d81ac08007fd428485ddded0ce2dbc2da4ff75ee43cc2a8db3c8c22007d77cc7fbddc21064101a1e9d313b8781e3988c73e8ac695ed174938124c04203cda251ab4bd4f867789db14f667297451efcdee8b7352028cd190ef061817e8083c870003db36c3876362c41d6f27e9a27f70db296de6be49b0b6abf84af7f8b60f3e7a5674ceac91e45b1fd5e7bab28eb6b6b66e214976af36d65c623c42f8be439e8ea33effa9504236fd58d2fb3ca2fdd04da2c2e92f7ec21b1ef0dca237cce05f28f6934f8eb5988f02868592ed17074e42fdf2055efcb41006b73be5e11a3712bc3754419f0245192e310e80c184f748f145950a5668d963a8a3364de7d753583ff383b7f91c58c3c1fda915a02da529e392a4d4a120554c56229335d0edf06c2c5ed929d873948e5fc018e55785d504bc9ca28ede26fa918ffb40c44d2463e27200f026a10c47287aba62a02d40c8d989edc630b3162f16f8871bed65ad0b578579e4df829208f7d2c994f0a8d210498fc234b9da19d1adba4ed7a34846f2a54f318246f3c18fd2071e65dfbd717951160fa653372d12485c7c04181f425223ba2c1c6d8b5aec8f33c8d3d00cc4580a807b3c8d7de5d8f85a4586ebc52fb3f3cf6ad0ef78b38d5fa4a921fd11a7e700c602c6616b262b7d678b88341aae344bc2b64e4bdc0fec7ffbe5f472de0d4bf7a3a9bf3e1b0e3894de06ea0beead64b29c7586f43bf1e6d6766ca68a42d18ebb8815dbadd4bb6cc25c0e49f41a5a529edb6292f19c2c639b453dbb3971ae29e27990de1c9e31131fede794fe217bda4983be739ad328b27fc7def7aee50921598819e5b9464e53f6ace40c5eeb041d6518bfb9553d019391fcb50ddbf74f7b9ac6bf41bf72e437ff3d56a19097c55554f22c365c68801f2b81c876a0b72c14f8ebcaf641b56abd5b2306281a00ebc115536fc1739477be3ad6f347c4b6dfc861c3af5a4f376dec4cb327b712320cb21896540fbf521fd0f26249fc910c4b5316f0605334b0b0ae750b94f20ebaa38602e86743b819edc5c7ff7aa9a505049f2df5d6890b3d1132e894c2b6d7618e96f1cd34e1d283a443c81ce159e3c78c7426914b0f70cf33be99546b03651eb60a0a544bc3294304d63350f378977685beb6419274e5a411ced7d784dcb518896764d70257f97d0fbd6957745af0c9e8f3aea8cf3604cad4a25fa0b2847628ce89e8827b67cc2cee06f9bd9a6fd11278cad52ce98ecdec044f11b7e6db951dd12db0d889fddfd0a1806820e61d44bfba3278675441d93b35f3997816a468832a220a51bd1be686a8eb302b13e0a4e691f94a0b50e66359ce2d69d268ec3bef771aa1368940d016191acaf82ea4afa651bccf24402ffd5e276edb5d5d3d49d164c53626a90a30920be3b02cefa78e077e4ec7555406142f399452b9edb16fc6ef953d94f45b30d9d19c83ffbe5a5280f28aff78bafb6468f5738cb5d8123fcbe54a869b619a4ee41d5850f4bddfffd8f38dec317f5cfa5e1c81b7ef6e14fcfff8e4a0656d8b6b2f42ed7d4de7d2e409a3ae5e5a17582b71eb9f3167dc523fe126fcf77128f691ffee3a0c10549a7c51dd2f114ca2f1a3e425fde85e9ce45c05c9a34f570d50a8af20981afe6614a6dd44e2677157cae933e4c6e1104f79bdac2bcba6b7e09195726c0499d43eea9032d1a65f18d8f45dd0be24a0d19c95f5c338ca18a637abc6835241355dcf8aa1a4b7ad0bf9186fa3130248a9521703b072ff8a039ad0a1825ff98cb4bf8cce8433f460e58a22c2ef12278903604568fe7c90a8ab78535c6238d29ee624512340d4c45cf3f827de43cae7555276666abc886918e5e7564ca6a0334ca7f9f39798ad0525f80bc64edd5a62f6eac95565d9849259948b4d63f2f8d9fa70290ec291091f996fb206702c519bc19ccdd0305db42299619b19ffed905bde89941552bfca9762c031d3a0d11f0537ad3c4cd56f65f347c45f0957a08a84a7e7b965e166cf5c1c5c0e3966d6271be64890748a6dcfc096ef342c1fe2dae566c1bd4038ba0fda50e65b3718cd71fa7bab948d131c2fb2027ad710e2a962f6a24c91e15b0022b16d82e1fd993adb3cc64d3096c5897424024509946796789f0466381c803c68040ca760f492780b9e77fca55c8a580ff125f82ea27e4ab6a3edc5dd7598f8f453c9211a8b68c6612d9cc40641303a46012ae5b00a42d0d51b63e34ead83b4f6019078c4a1a5e5b5ff4c8768f22b4433abb738552c1869eb63d5973fbdf228b1d50b2dcb0c6ce4987ed12a057aae69ab16b21e7ac608e94ee6474be17705fedf47f7600fffd79c75d13f0bcdd52a05ca42224ffd332feb686ef9a7b1476f8bde5698207e777e74d0bd62f4037ef400c5ac1a2126ff4ae1189d2b7903eb1b2ba6855304a633799f38759a3193030cf8405e57151f9d37ba539555061cdde513dcc13c6092533995362ba9bfa00989b56a3109fa65986bbcef7efd87e85e122109dc4606e45f441213018eaff72026647093805e21a5e77d19fbe208a335a04d9bc89d1d035fef18ee6ffc560dc4b1d228903e8d919961ae1f6642d1be6d7d2083470b913cf18c04a0e9a60146c5811fef17c1ade070ec4e8285881e6f71491f169d438fee2952374fcef3c4f025aa559d135e59d3da4d6f559235f01526c1594634d97f0abadcaa0ccd792a48458e778647fd1720229a8820792968bc53cea6f6647955f5e2380ae5ca976c308ef85cba54b4ad0f9bdc20afc7a923242494a6092124f52026781781b62bf459ea6b09b4a968259cfc867b825c4f0cf6621d87faa952f5fcf4ef7cf6130700cab72b002af8644c41ed5dc257613f294d8ef4dc86b8d25337aaa2b72b92c9f0a4dac420f0d43f4939d92d34c30f7426d68c3c23bda03b8437ad5c031a2202ad3423140e8e5dc4e0a7f9bc456ab6a8ac63bfd2ae590d1d4cc0dc65a1599fa8a5e033e1780db8927ab38d569de3ffd5ea79a0bc5ff9dd79b706364a7cfb8a457a1a132a5786633f7012e88803b9c08e56fc2e693e323f2f5608d320d90213e3b69cdbe10a0880e95ed99f56d7bfaf0d224f3a27b90e6fc6a127ec05fd6ecd976d936d6ef5287a8ef8947ab2111b1fb7f7747f8ad8da8221c3a069fc5a4cde3dcbb3231b7ac71c5fa8951adb7b7a33f718913ea2f387d924363d63f00fd4f33bdba5344a776ac42db1c326352bcfee8bb93c8e42c7871c428783499c20b2568c2b500f5966eaa8397fd46786f71474aabc4be6ca220b41e29853c7757074d3e52547acf3c0e1064cdce0651415444aa1a30b1cf5b5419a8ad6168cbdb94e57d9c8758a2d35984f99e50c40760a2deddd9147d7b2e5c4dc7be8601ff68382a50a9ec47c95083ac6dc0776b4eef4031c59373f9513fffa0ba228a3a87dbbad29e13244229d31eedafd407705d3ac3abbff9bd35c6b3c9a0f591d83a33cb5561abe6bf73aabe3e8687a81135aaae22720da6eb45b25580fd372dbf50e65d19ad2272d395482e12fe763bd8e70b618a8a18cfd99663121527ae7e178b64cf9a5774bb7ad792df8750d7daec06bb3a584d1bac90f48ff8507d65129c084fe038141d0f47c77f3c4971ef57b1ee623d94be3b79bd772fa85f7b703183d9bce97b2cdc9b90cdbc9f99750153e895087defd534c64f2d7041ba8062bff3aa91da4928e15680031663f1b71b6c0fed554f96e9f4d682dcc2bb2024f4a2bd223a3b4330a0d0a26d4839ad6a15e9f7d4f14112195a99bf4cc59d4b6d85d08b613c78782a0e00c6d262714219c487a8cee86e76fbe7ad25edce0168fe74f0afd5442aaacaf4ff6a58eaf9d121da8f11b852f435e51abfe8e4ef9aca71d4d4e421897f4c55dc87739efffdcea6fa558195cb0b904cc8275228e49d24eb525876446b8ed5bfc3477200ad03e0fe8832fe3ac518e24491f71afc30d6c2c1196ce3e47c7b50e9fd66c665797c83d93d9d35d8de61a913be8c8d632490ef7bd05e5520f459f6c92a261187dd55e68bfaec191ed258dff13c8f4d64c466c8cbf96d79ef6fe122caf23b9b9615b78bf73c8e8942ebcf3675ef3a8f6e8c237ec1bbef8eac25d7e49969f0195746d9a67cd1b986e3f5d0d89a9e6f656ce5f2e7ce03a2baf27267a7e6f09310a7651f8f8aee063594c4440f2ccf04e8967b235cf56fe49e0b60b4c8ab77f71a3cbefab45478ad36cdbcbf8e3f2a1f121215f737b9458a4d2404505b82defabe7a869aa8aa70ca588745bab1648ca4e53ed45cee83ea3120600609d8c0dbfc433327463544420d7ce19913b7a823bf9b69297ba85828e7c2a37da5e982fd0d97f0f281c6089934d7230aacb6d67855069af951c43c3a3d222f87439ae50ba1b3056ca0bc874dd441737792dabc48ee1c4fcf811b32902ce40c8b4b69c536fdfbce9999dc4cfdabbb4d70ba0e73e035c1b2262321ad9324ae6399806c5e3aff79ea7264f8e7680f6a09698902876092bd73d3f46820aaa4085d068f44f669b9785189726b045c4b49124bd33ad1bc0541ba9812909949fe21c7d6f6989d5bdbf3f2c9413fe335e84d64c95d239b5e886a7116dde327b746dc584330fdf579cbc30b8a7e824fe7a0153744706d4b660865f56a456592942f1510e40229ea873cc3af0244a83103fee04b4405be89db8fd75e786d870b41af1a0ef93a72e5f3a97039c3bef94456ca3e21d8c6e84784486ea8518ddfdcc6826d279de7e663f16b8014fd5e5fca84529c89e79f8d928b85ca356c1967b5dd879fbdc0d20384a0e8fdd586868d37796393b5bf81183972b8d74ba0893cbee7ccdba163b73778075b9d80f0451622ea45bf2134a3adbe61380ddc09c6937607abbe39f86f920f82122b35bf2df437926f957c02e548ee01a61125a1396d6e75bd3e2ea8c68404964b2cb45c644a028d90a04eea2bcb16d0d3bcfb17e45e75c0ad623064306ffaca7b1b657e85a0a8a2dbd12e2957a03c9082b039278609e9c46b1610272b20d72edcc36a61ae42b580d5b8296fa28dd9943ddd88ed32caf600a8ebbaa056da3229fda211f5690b68958effac0fb849c3d3cc65d5af3bb6b8bbe10e03fbcc08987e1f5a129355aaa5cbbf7922097794d7a7eb211851404b3ef8d328292218f9d280ccbdb222de78c10bfbbd6deff917570cb3fa5c7a2a0e2a47f7b15138e1cd51b6b2c41dfb204b96bbe3c04050f771d1e9a7a531966da1b43832e325cb5527244921cc0c6c575652dcd7c4868b5d0065e4910ba5d9588d615bb5aa150b65352b3d1846f06a95b05f1f2a8f922f61f10dddac74c09a7821c125a92c66ef53173d45e93d2c1b8a4537e9a056444ac12e71784e83f4dcedc0f0b945ad22e9e6d76f5a66217cda79435da438d4294018548154319a2f4ba7e2c047a382529505fd9d2424ef88ca097f5768e0238928c6a5e2d00576633c270dd642c22a1b5dacf45188f3eb631fa8e00167a858ec44a97a687e382a980b92b8e6c785861e28dca231ceffa58760fffdeeec78f82b60ca390450bb6d808fba87ba71c8a8f7c931f7c887682e61e5aeef782a12853db9b973addec024faf28a42331c986da2d90cb5a2c4a204321db79544037182c368ad844cf7e294aa8124e6ff4cac25b117c6cc39adcf29c460a5fe6bdd203532bb716cb3a462b716c2efbcd92463e495eed56140cb4fdead6f65ba096166bb201822fb4b11664592c29b63b54f4b63e9e4909cafe5f462d160f8cb6c4e7a083e5f99371df9bd2cb4dec23ad6376eb24f1611d393302ccebd6eb95e0470c25b227328c791de911b2f6eeac31d35d1ba9215b3f5bcebf05d0d178ae6ab729b39c7dbad22d44ddf4ec797852c1790ae6312d93ec8af26bb66c2d1a5eee2e3dbf61e1f077fc35173af2d904199f580807487ddc6808e88086b1cf063641671a7cc27b80836103e573a1087bb724e80febf8bb6eaa2108161a2b0ae038506a0c38f49c9f333c9225e62cd4b82c890d4e8f5e258301ff64ce5ced22039379df9a95764ee6e5608f6e4146ba577e7aaf0f91b46e48c28ee90cc26d0bd2765d0170f6b874e15989073c7328c103ae7cb79aae619c5e66a12fe48e8d9c200a11a1a20b6f4120900806800f62014a55d0302d3532b4d5fe9bcead16fea05e82294c263ce5857693a75dbfd5c8e1db596fecd9a00a2fb2648fa120bcbb85405631a810be70bc29d9d20675486de68d55c19e7b35ee7c119cb38c302c2b1117482304fa53f4be710c25d25f6541037bb43aa25d1fed9b3ba6b3a37ef8d6125d53aae0a0f698afda4917f0c1a9d9f4cb5e833f0e2c9906b0ef111b3d8cf20fc16ef3695bb758115d30af5266bcebf1de188f1b31b4a0d591b7edf4b25b0b155cca98e901dc01cd32489a1ee9add6a13b84b8a9bd7499da7180ac9441f99af53b22775d1473911455d70e00b77196694d12d8a91f1c7f7559f9c79e41afcfbfa5d15ded230ed67c3a3818b2e9ad8a72789545c0b7b215d327d52f8100e44cbd22622108eba723846212b1b6444f7293b5e918567925d00fa0a4bad8f59fe1a8b8233fdcc0e6b04bfd22b139980c7bd1b6cd4f421560baef888a4a727e9a2b6f615195f633f6597b5ffbed246a4e0c45b220d23d14096e57af5608d1cd51000663f1f5fdc703bc571084d0a648592545d98ea32b9892dc6b81a0a5ae7587113d6e1785feb0f5682f55acbdeac2572a8ec7f89062591b8ce94e77fa63d188d71fc18503d1f8a1b4ac803e5ceaad111555c5553afca3379de2b5c1e3f1ba9edf5ab4b6e570b40a619aeceb25d8cafd5a39f93cad291dd6b63129f17314f232259cce1357208a0f1caf03f84bf772baba20eb872eafe00a9b273f1394a664b630331c6fb492a002057a9f93c794dd38e8d107750e1f5ab477c88b0647d67d15a1d6070c60e9dd8eac232d1c467722e17b82ddd89f9c0be81ad6e334efd9b1ad1fbd5bb6f358c9f1586cc38865dbe0fa007376fcf2edf53dfb6ae51df7e5743dc78209444a0e017db35889cd05db3e68396745ef3ea3f7bbe767de6f17ba062aab6e06c166e2583f62e1117957fb607677e38c03a3718d3b6b89be107b2607e7e4166af726b1cf24efdd9469e8327e3abee888ad461164bbba1260b43b85995ed5faa18d3162e880391958a88a1acdceaabdff61887ef0941497ab7731283c867a0275652ac3e87c8a46a7010fffb13052e49cc2bf20cfefaf7eba6c254899aecd7af06d8796c0cd25cfc0f9234f6e6180aa35b3bcc2beab3b08f16f8dd54cc95ed486688a685e93bedbb47b5e5c973d92aab436696dc56b4af55080cbf4f05d0972c23eca0849283da18a281e143926d758e718fa45ecc24ff774d2c2c47d36374e643863d234bc4596968592472284086220f6e369def97d6de51abc948ddd10afd2016d3fca95215c1523c8ba2f626ff4fb4886f65f9e5c95575711f9fe121dbb5d6aab3566c8dd773ecace00d19a0289ddcfbe79bbca3b3f9538460c6b01baf6ff744f8f1f6ea5c9e11bc52cf121e69f9520d60f992414872965e0602cfdeab145bba5eeba53afccf58f44a33e15df27c8855f1abaac7d78eef9ab7b1f348a0ac224584bcccd75aca63f1b2ad4b98b8658ed7e1a5ff7b65a508b096c1eb6a3332643739bf04cae3370faed282a35b6d6b70bed5cdc5169364d78c038bf9e447f1627b8769338b239e36281de54a2003ceabaaaff71ffa10d2f463855c33c71bc7e06da3be9ed07902564b7eb61fa27f9082f16f55bb6e5fe7999630384133dbfdadf46e2bc33ff718e80150499aec48906d6086e4f04651c2b893e708f31ba27f2025fdc8397c84ff71406842c4e6dc7c8a01ef1c243247ca4333d589883784993128368cbe581a9eadc16c4cc33b5c56e22f54b8edc356916b45affa9d80fd35d47dfa79b301268e5499ccc30debc52ee730f2c42ee19e5a5dffae7cfd4a2b99c4ed08c68f446b07a1ef6f3868410048702467850f5751ce0a26d0ace3a3985d0f0b3c07dd340e33769ec74b0c529ca73a5495a8b8c3e7ab508dbe2725887125dcca865a4059438a0c7995fa1529e4ea44e94799b6237d7121499692a4d335b53cce1e32404380e0bc185f44bc33b1bce335dfc54eb288ab5a5ef27550a37e46ef2ba9e23bddbf5e177b55e691e09b519cd42331ccf5be51ebb146fa56fa48bed6d1aa7be90bb0f671ff4ed038ced9d8371fef9cc6f191881a34aa6952654970a530284cf1d42807149cf0cf3de63d836cb0e90db35424ed853c15deb9cf98caee3ea972ff7d27f2416953ba0dd20d7d16d3b09e52a43654b092a0a14406c8b2abf27582fe00a18d6f7bbec02d3f099cf5c8e70fdc885472dd7e129317a953ed428e9a520db9aeec81908e3fead02db61f360338ccce63771054b9566733fd0f4d5047be9fc5a333ea626d88d1e8d398b83080f48a408864e198c4e2b3706f6e80771164742fe405ee3e50f776ad837733f65dc86791594449eb046abb15c2b42b23e644aaac54b73aace855a4229540165bf5b8d94e0963c7cf3359b467b00db127e6f6194f2926da6eb0bd73dd1d2dee116dbd1d1dd1fd7f89123dda86086a23450a7af34fdb79d873f8671cd381d8c0f80fbe6cf1c697f2be6171719f3f0c3ce381482595fa9a78ab87cc9658d8e5b83267caf2fb57a75a88faabcdaea0443afd8debbf68cda278c18ecc8dc89527ac4638aaea8fec6d016813577c6f26eb5d940ddc532657662932721b74113d48d7ab8b9a6d08623c1292338e2138cb48020adc7142a5acbb4ae25173adce9a6f20f55be1adb7e197adf81fde7ed8b0e82969a5392520dde77ea5c1850a5be72b381f05d546e884229947de4d52e69e9477ba3e737f831544b25e774d35be90e8f37dbf59ee84e329974d171193421b9d1f42b6510c61043ae94ff38eb93ce706119f329ac4c847f2c3add75faf49b5d0a395e6604a6b8731aca6180f07fefaf7d26891789701a8c92d7adfa1ccbcaf5cc716ba89f2ef26fcce88707c6571eaa2e55805d6c8431a78ccaa0e00af44877e901b32862e780c560bb6fe579b79167fcab1b9d958ae531addbc8a01179f208a183e532d6d28a6d5e697c2ce30be155e6cfc9c96db2c450cb90ed38e0377658d75b2a0cabfaacf48a2e90664da793aca09d936912c31b18bcf125f8de9e861a6b61257940d8fdf5c8fb3f961500f6491d10b09e56e9037ec9fc9c5b3f4ec4a08a93f643927386fb1f7b82f770976a2920c3dd51c0b8664c3c1fc5f5577e8c5784cb55bf5dc14e180d216c2d3bf37774b6705365ddf7420395c4f8cd57033673ede08c792acd5a6b9942aaaf567ebda59b97fa3740a8513c2e98d9f9f2d3f635366bd735646fbf998b43334649461f7217193ffdc382dcf221b2425943897a8453c8c20494c59553f9f2dde9e6eb012554bbb0d98966b69a2ffe9818c0f754d194b0acbaa9898df1088a810f9893bbede7dad51d58457970eb5c37e07ad7f3a87ec7fae498e0efff63cf26c847dec6d0b43f71e513aacd555ce143cbf0b2de1ae1cfa17d1b4a453900e77a7042b455ac3010f81d8f0b1020e48576fbce61a5cdcfdd6a4d8ba6cf14a72ea055fac65fd6603ee1f6132e558cbaf0293e5f5e4458d2f6f57b159efba249035e623508b6ebf4212880537054d2638794f65aaa8017b1f7982b3196940545698acf38825e190a4d7268936d38994caad4700a22b921d8f0d6372ef9543cad36d2c8db167953e174b33e0d71375cc296a8dce8b4f66a3ab868106131e54cb21e3a355baf2c2d04da339f4cb36cece67733a25e54195ffdb08eae0350b891fbefa1bf40f8d6c5513615f3ff0ff8a3be412c2a747327b6f737fd8790f5e0078615abc18d3428362aa08763522cfa86e07ad9eb7fcbfa9ab43f7329fc910353ba011cf50b5519ae25f0e6741357fafde7acac2807104c9acfbe107bcb1cd9b1aec4feccf4531585bf8fd1a9b1f3a14267f86ab85ada513e80795cb990bcde6edd3c214cf431902ef30e6c7c5c83f85fa64d81e62b969f925fda617700727896341006b5cfd71cec0966bdea79116667600b7b8fca3f7f5c38a598c544eb4b9c4ed471dd82012e8a7c5ba1adcfa7dd8ba392fc7ae6e2b716743aef3ec95544ed8557085520c693341f11519377fff8dcad507808df581eea2eecd79a526324d2c9de1c05645fdf97976a695738fa74cb609f13c38ea66361a2d05b085fb33b72d973dcfdcbd0e3a88c15cc8981d57b9493fc8d17368bba45767940f" +} diff --git a/test/contract/sequencerInbox.spec.4844.ts b/test/contract/sequencerInbox.spec.4844.ts new file mode 100644 index 000000000..c8752e74f --- /dev/null +++ b/test/contract/sequencerInbox.spec.4844.ts @@ -0,0 +1,598 @@ +/* + * Copyright 2019-2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-env node, mocha */ + +import { ethers } from 'hardhat' +import { BigNumber } from '@ethersproject/bignumber' +import { JsonRpcProvider, TransactionReceipt } from '@ethersproject/providers' +import { expect } from 'chai' +import { + Bridge, + Bridge__factory, + GasRefunder__factory, + Inbox, + Inbox__factory, + MessageTester, + MessageTester__factory, + RollupMock__factory, + SequencerInbox__factory, + TransparentUpgradeableProxy__factory, +} from '../../build/types' +import { applyAlias } from './utils' +import { Event } from '@ethersproject/contracts' +import { Interface } from '@ethersproject/abi' +import { + BridgeInterface, + MessageDeliveredEvent, +} from '../../build/types/src/bridge/Bridge' +import { Signer, Wallet, constants, utils } from 'ethers' +import { + keccak256, + parseEther, + solidityKeccak256, + solidityPack, +} from 'ethers/lib/utils' +import { Toolkit4844 } from './toolkit4844' +import { SequencerInbox } from '../../build/types/src/bridge/SequencerInbox' +import { InboxMessageDeliveredEvent } from '../../build/types/src/bridge/AbsInbox' +import { SequencerBatchDeliveredEvent } from '../../build/types/src/bridge/ISequencerInbox' + +describe('SequencerInbox', async () => { + const findMatchingLogs = ( + receipt: TransactionReceipt, + iFace: TInterface, + eventTopicGen: (i: TInterface) => string + ): TEvent['args'][] => { + const logs = receipt.logs.filter( + log => log.topics[0] === eventTopicGen(iFace) + ) + return logs.map(l => iFace.parseLog(l).args as TEvent['args']) + } + + const getMessageDeliveredEvents = (receipt: TransactionReceipt) => { + const bridgeInterface = Bridge__factory.createInterface() + return findMatchingLogs( + receipt, + bridgeInterface, + i => i.getEventTopic(i.getEvent('MessageDelivered')) + ) + } + + const sendDelayedTx = async ( + sender: Signer, + inbox: Inbox, + bridge: Bridge, + messageTester: MessageTester, + l2Gas: number, + l2GasPrice: number, + nonce: number, + destAddr: string, + amount: BigNumber, + data: string + ) => { + const countBefore = ( + await bridge.functions.delayedMessageCount() + )[0].toNumber() + const sendUnsignedTx = await inbox + .connect(sender) + .sendUnsignedTransaction(l2Gas, l2GasPrice, nonce, destAddr, amount, data) + const sendUnsignedTxReceipt = await sendUnsignedTx.wait() + + const countAfter = ( + await bridge.functions.delayedMessageCount() + )[0].toNumber() + expect(countAfter, 'Unexpected inbox count').to.eq(countBefore + 1) + + const senderAddr = applyAlias(await sender.getAddress()) + + const messageDeliveredEvent = getMessageDeliveredEvents( + sendUnsignedTxReceipt + )[0] + const l1BlockNumber = sendUnsignedTxReceipt.blockNumber + const blockL1 = await sender.provider!.getBlock(l1BlockNumber) + const baseFeeL1 = blockL1.baseFeePerGas!.toNumber() + const l1BlockTimestamp = blockL1.timestamp + const delayedAcc = await bridge.delayedInboxAccs(countBefore) + + // need to hex pad the address + const messageDataHash = ethers.utils.solidityKeccak256( + ['uint8', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + 0, + l2Gas, + l2GasPrice, + nonce, + ethers.utils.hexZeroPad(destAddr, 32), + amount, + data, + ] + ) + expect( + messageDeliveredEvent.messageDataHash, + 'Incorrect messageDataHash' + ).to.eq(messageDataHash) + + const messageHash = ( + await messageTester.functions.messageHash( + 3, + senderAddr, + l1BlockNumber, + l1BlockTimestamp, + countBefore, + baseFeeL1, + messageDataHash + ) + )[0] + + const prevAccumulator = messageDeliveredEvent.beforeInboxAcc + expect(prevAccumulator, 'Incorrect prev accumulator').to.eq( + countBefore === 0 + ? ethers.utils.hexZeroPad('0x', 32) + : await bridge.delayedInboxAccs(countBefore - 1) + ) + + const nextAcc = ( + await messageTester.functions.accumulateInboxMessage( + prevAccumulator, + messageHash + ) + )[0] + + expect(delayedAcc, 'Incorrect delayed acc').to.eq(nextAcc) + + return { + baseFeeL1: baseFeeL1, + deliveredMessageEvent: messageDeliveredEvent, + l1BlockNumber, + l1BlockTimestamp, + delayedAcc, + l2Gas, + l2GasPrice, + nonce, + destAddr, + amount, + data, + senderAddr, + inboxAccountLength: countAfter, + } + } + + const fundAccounts = async ( + wallet: Wallet, + length: number, + amount: BigNumber + ): Promise => { + let key = wallet.privateKey + const wallets: Wallet[] = [] + + for (let index = 0; index < length; index++) { + key = keccak256(key) + const nextWallet = new Wallet(key).connect(wallet.provider) + if ((await nextWallet.getBalance()).lt(amount)) { + await ( + await wallet.sendTransaction({ + to: nextWallet.address, + value: amount, + }) + ).wait() + } + wallets.push(nextWallet) + } + + return wallets + } + + const setupSequencerInbox = async ( + fundingWallet: Wallet, + maxDelayBlocks = 10, + maxDelayTime = 0 + ) => { + const accounts = await fundAccounts(fundingWallet, 5, utils.parseEther('1')) + + const admin = accounts[0] + const adminAddr = await admin.getAddress() + const user = accounts[1] + const deployer = accounts[2] + const rollupOwner = accounts[3] + const batchPoster = accounts[4] + + // update the addresses below and uncomment to avoid redeploying + // return connectAddreses(user, deployer, batchPoster, { + // user: '0x870204e93ca485a6676E264EB0d7df4cD0246203', + // bridge: '0x95491D63100cc7a21155247329007ca294fC752B', + // inbox: '0x00eb941BD8B89E0396A983c870fa74DA4aC5ecFB', + // sequencerInbox: '0x87fEe873425A65Bb2A11dFf6E15B4Ce25e7AFccD', + // messageTester: '0x68BCf73c6b36ae3f20b2fD06c2d4651538Ae02a6', + // batchPoster: '0x328375c90F01Dcb114888DA36e3832F69Ad0BB57', + // gasRefunder: '0x33B1355B2F3BE116eB1c8226CF3B0a433259459C' + // }) + + const rollupMockFac = new RollupMock__factory(deployer) + const rollupMock = await rollupMockFac.deploy( + await rollupOwner.getAddress() + ) + + const reader4844 = await Toolkit4844.deployReader4844(fundingWallet) + + const sequencerInboxFac = new SequencerInbox__factory(deployer) + const seqInboxTemplate = await sequencerInboxFac.deploy( + 117964, + reader4844.address, + false + ) + const inboxFac = new Inbox__factory(deployer) + const inboxTemplate = await inboxFac.deploy(117964) + + const bridgeFac = new Bridge__factory(deployer) + const bridgeTemplate = await bridgeFac.deploy() + await rollupMock.deployed() + await inboxTemplate.deployed() + await bridgeTemplate.deployed() + await seqInboxTemplate.deployed() + + const transparentUpgradeableProxyFac = + new TransparentUpgradeableProxy__factory(deployer) + + const bridgeProxy = await transparentUpgradeableProxyFac.deploy( + bridgeTemplate.address, + adminAddr, + '0x' + ) + const sequencerInboxProxy = await transparentUpgradeableProxyFac.deploy( + seqInboxTemplate.address, + adminAddr, + '0x' + ) + const inboxProxy = await transparentUpgradeableProxyFac.deploy( + inboxTemplate.address, + adminAddr, + '0x' + ) + await bridgeProxy.deployed() + await inboxProxy.deployed() + await sequencerInboxProxy.deployed() + + const bridge = await bridgeFac.attach(bridgeProxy.address).connect(user) + const bridgeAdmin = await bridgeFac + .attach(bridgeProxy.address) + .connect(rollupOwner) + const sequencerInbox = await sequencerInboxFac + .attach(sequencerInboxProxy.address) + .connect(user) + await (await bridgeAdmin.initialize(rollupMock.address)).wait() + await ( + await sequencerInbox.initialize(bridgeProxy.address, { + delayBlocks: maxDelayBlocks, + delaySeconds: maxDelayTime, + futureBlocks: 10, + futureSeconds: 3000, + }) + ).wait() + + const inbox = await inboxFac.attach(inboxProxy.address).connect(user) + + await ( + await sequencerInbox + .connect(rollupOwner) + .setIsBatchPoster(await batchPoster.getAddress(), true) + ).wait() + await ( + await inbox.initialize(bridgeProxy.address, sequencerInbox.address) + ).wait() + await (await bridgeAdmin.setDelayedInbox(inbox.address, true)).wait() + + await (await bridgeAdmin.setSequencerInbox(sequencerInbox.address)).wait() + const messageTester = await new MessageTester__factory(deployer).deploy() + await messageTester.deployed() + + const gasRefunderFac = new GasRefunder__factory(deployer) + const gasRefunder = await gasRefunderFac.deploy() + await gasRefunder.deployed() + // fund the gas refunder + await ( + await deployer.sendTransaction({ + to: gasRefunder.address, + value: parseEther('0.2'), + }) + ).wait() + await (await gasRefunder.allowContracts([sequencerInbox.address])).wait() + await (await gasRefunder.allowRefundees([batchPoster.address])).wait() + await (await gasRefunder.setExtraGasMargin(35000)).wait() + + const res = { + user, + bridge: bridge, + inbox: inbox, + sequencerInbox: sequencerInbox, + messageTester, + batchPoster, + gasRefunder, + } + + // comment this in to print the addresses that can then be re-used to avoid redeployment + // let consoleRes: { [index: string]: string } = {} + // Object.entries(res).forEach(r => (consoleRes[r[0]] = r[1].address)) + // console.log(consoleRes) + + return res + } + + it('can send normal batch', async () => { + const privKey = + 'cb5790da63720727af975f42c79f69918580209889225fa7128c92402a6d3a65' + const prov = new JsonRpcProvider('http://127.0.0.1:8545') + const wallet = new Wallet(privKey).connect(prov) + + const { + user, + inbox, + bridge, + messageTester, + sequencerInbox, + batchPoster, + gasRefunder, + } = await setupSequencerInbox(wallet) + + await sendDelayedTx( + user, + inbox, + bridge, + messageTester, + 1000000, + 21000000000, + 0, + await user.getAddress(), + BigNumber.from(10), + '0x1010' + ) + + const subMessageCount = await bridge.sequencerReportedSubMessageCount() + const balBefore = await batchPoster.getBalance() + await ( + await sequencerInbox + .connect(batchPoster) + .functions[ + 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' + ]( + await bridge.sequencerMessageCount(), + '0x0042', + await bridge.delayedMessageCount(), + gasRefunder.address, + subMessageCount, + subMessageCount.add(1) + ) + ).wait() + expect((await batchPoster.getBalance()).gt(balBefore), 'Refund not enough') + }) + + it('can send blob batch', async () => { + const privKey = + 'cb5790da63720727af975f42c79f69918580209889225fa7128c92402a6d3a65' + const prov = new JsonRpcProvider('http://127.0.0.1:8545') + const wallet = new Wallet(privKey).connect(prov) + + const { + user, + inbox, + bridge, + messageTester, + sequencerInbox, + batchPoster, + gasRefunder, + } = await setupSequencerInbox(wallet) + + await sendDelayedTx( + user, + inbox, + bridge, + messageTester, + 1000000, + 21000000000, + 0, + await user.getAddress(), + BigNumber.from(10), + '0x1010' + ) + const subMessageCount = await bridge.sequencerReportedSubMessageCount() + const afterDelayedMessagesRead = await bridge.delayedMessageCount() + const sequenceNumber = await bridge.sequencerMessageCount() + + const balBefore = await batchPoster.getBalance() + const txHash = await Toolkit4844.sendBlobTx( + batchPoster.privateKey.substring(2), + sequencerInbox.address, + ['0x0142', '0x0143'], + sequencerInbox.interface.encodeFunctionData( + 'addSequencerL2BatchFromBlobs', + [ + sequenceNumber, + afterDelayedMessagesRead, + gasRefunder.address, + subMessageCount, + subMessageCount.add(1), + ] + ) + ) + + expect((await batchPoster.getBalance()).gt(balBefore), 'Refund not enough') + + const batchSendTx = await Toolkit4844.getTx(txHash) + const blobHashes = (batchSendTx as any)['blobVersionedHashes'] as string[] + const batchSendReceipt = await Toolkit4844.getTxReceipt(txHash) + const { + timestamp: blockTimestamp, + number: blockNumber, + baseFeePerGas, + } = await wallet.provider.getBlock(batchSendReceipt.blockNumber) + + const timeBounds = await getTimeBounds( + blockNumber, + blockTimestamp, + sequencerInbox + ) + const dataHash = formDataBlobHash( + timeBounds, + afterDelayedMessagesRead.toNumber(), + blobHashes + ) + const batchDeliveredEvent = batchSendReceipt.logs + .filter( + (b: any) => + b.address.toLowerCase() === sequencerInbox.address.toLowerCase() && + b.topics[0] === + sequencerInbox.interface.getEventTopic('SequencerBatchDelivered') + ) + .map( + (l: any) => sequencerInbox.interface.parseLog(l).args + )[0] as SequencerBatchDeliveredEvent['args'] + if (!batchDeliveredEvent) throw new Error('missing batch event') + + const seqMessageCountAfter = ( + await bridge.sequencerMessageCount() + ).toNumber() + const delayedMessageCountAfter = ( + await bridge.delayedMessageCount() + ).toNumber() + + // -2 since we add a message to the from the sequencer inbox + const beforeAcc = + seqMessageCountAfter > 1 + ? await bridge.sequencerInboxAccs(seqMessageCountAfter - 2) + : constants.HashZero + expect(batchDeliveredEvent.beforeAcc, 'before acc').to.eq(beforeAcc) + // -2 since we add the batch spending report + const delayedAcc = + delayedMessageCountAfter > 0 + ? await bridge.delayedInboxAccs(delayedMessageCountAfter - 2) + : constants.HashZero + expect(batchDeliveredEvent.delayedAcc, 'delayed acc').to.eq(delayedAcc) + const afterAcc = solidityKeccak256( + ['bytes32', 'bytes32', 'bytes32'], + [beforeAcc, dataHash, delayedAcc] + ) + expect(batchDeliveredEvent.afterAcc, 'after acc').to.eq(afterAcc) + + // check the spending report was submitted + const inboxMsgDeliveredEvent = batchSendReceipt.logs + .filter( + (b: any) => + b.address.toLowerCase() === sequencerInbox.address.toLowerCase() && + b.topics[0] === + sequencerInbox.interface.getEventTopic('InboxMessageDelivered') + ) + .map( + (l: any) => sequencerInbox.interface.parseLog(l).args + )[0] as InboxMessageDeliveredEvent['args'] + + const spendingTimestamp = + '0x' + inboxMsgDeliveredEvent.data.substring(2, 66) + const spendingBatchPoster = + '0x' + inboxMsgDeliveredEvent.data.substring(66, 106) + const spendingDataHash = + '0x' + inboxMsgDeliveredEvent.data.substring(106, 170) + const spendingSeqMessageIndex = + '0x' + inboxMsgDeliveredEvent.data.substring(170, 234) + const spendingBlockBaseFee = + '0x' + inboxMsgDeliveredEvent.data.substring(234, 298) + const spendingExtraGas = + '0x' + inboxMsgDeliveredEvent.data.substring(298, 314) + + expect( + BigNumber.from(spendingTimestamp).eq(blockTimestamp), + 'spending timestamp' + ).to.eq(true) + expect(spendingBatchPoster.toLowerCase(), 'spending batch poster').to.eq( + (await batchPoster.getAddress()).toLowerCase() + ) + expect(spendingDataHash, 'spending data hash').to.eq(dataHash) + expect( + BigNumber.from(spendingSeqMessageIndex).eq(sequenceNumber), + 'spending seq message index' + ).to.eq(true) + + if (baseFeePerGas == null) { + throw new Error('Missing base fee') + } + expect( + BigNumber.from(spendingBlockBaseFee).eq(baseFeePerGas), + `spending basefee: ${BigNumber.from(spendingBlockBaseFee).toString()}` + ).to.eq(true) + expect( + BigNumber.from(spendingExtraGas).gt(0), // blob spending is normalized into extra gas + `spending extra gas: ${BigNumber.from(spendingExtraGas).toString()}` + ).to.eq(true) + }) + + const getTimeBounds = async ( + blockNumber: number, + blockTimestamp: number, + sequencerInbox: SequencerInbox + ): Promise<{ + maxBlock: number + minBlocks: number + minTimestamp: number + maxTimestamp: number + }> => { + const [delayBlocks, futureBlocks, delaySeconds, futureSeconds] = + await sequencerInbox.maxTimeVariation() + return { + minBlocks: + blockNumber > delayBlocks.toNumber() + ? blockNumber - delayBlocks.toNumber() + : 0, + maxBlock: blockNumber + futureBlocks.toNumber(), + minTimestamp: + blockTimestamp > delaySeconds.toNumber() + ? blockTimestamp - delaySeconds.toNumber() + : 0, + maxTimestamp: blockTimestamp + futureSeconds.toNumber(), + } + } + + const formDataBlobHash = ( + timeBounds: { + maxBlock: number + minBlocks: number + minTimestamp: number + maxTimestamp: number + }, + afterDelayedMessagesRead: number, + blobHashes: string[] + ) => { + const header = solidityPack( + ['uint64', 'uint64', 'uint64', 'uint64', 'uint64'], + [ + timeBounds.minTimestamp, + timeBounds.maxTimestamp, + timeBounds.minBlocks, + timeBounds.maxBlock, + afterDelayedMessagesRead, + ] + ) + + return keccak256( + solidityPack( + ['bytes', 'bytes', 'bytes'], + [ + header, + Toolkit4844.DATA_BLOB_HEADER_FLAG, + solidityPack(['bytes32[]'], [blobHashes]), + ] + ) + ) + } +}) diff --git a/test/contract/sequencerInboxForceInclude.spec.ts b/test/contract/sequencerInboxForceInclude.spec.ts index 43634737c..125379e69 100644 --- a/test/contract/sequencerInboxForceInclude.spec.ts +++ b/test/contract/sequencerInboxForceInclude.spec.ts @@ -26,6 +26,7 @@ import { Inbox, Inbox__factory, MessageTester, + RollupMock__factory, SequencerInbox, SequencerInbox__factory, TransparentUpgradeableProxy__factory, @@ -38,6 +39,8 @@ import { MessageDeliveredEvent, } from '../../build/types/src/bridge/Bridge' import { Signer } from 'ethers' +import { Toolkit4844 } from './toolkit4844' +import { data } from './batchData.json' const mineBlocks = async (count: number, timeDiffPerBlock = 14) => { const block = (await network.provider.send('eth_getBlockByNumber', [ @@ -219,12 +222,25 @@ describe('SequencerInboxForceInclude', async () => { const admin = accounts[0] const adminAddr = await admin.getAddress() const user = accounts[1] - const dummyRollup = accounts[2] + // const dummyRollup = accounts[2] + const rollupOwner = accounts[3] + const batchPoster = accounts[4] + // const batchPosterManager = accounts[5] + const rollupMockFac = (await ethers.getContractFactory( + 'RollupMock' + )) as RollupMock__factory + const rollup = await rollupMockFac.deploy(await rollupOwner.getAddress()) + + const reader4844 = await Toolkit4844.deployReader4844(admin) const sequencerInboxFac = (await ethers.getContractFactory( 'SequencerInbox' )) as SequencerInbox__factory - const seqInboxTemplate = await sequencerInboxFac.deploy(117964) + const seqInboxTemplate = await sequencerInboxFac.deploy( + 117964, + reader4844.address, + false + ) const inboxFac = (await ethers.getContractFactory( 'Inbox' )) as Inbox__factory @@ -252,17 +268,14 @@ describe('SequencerInboxForceInclude', async () => { adminAddr, '0x' ) - const bridge = await bridgeFac.attach(bridgeProxy.address).connect(user) const bridgeAdmin = await bridgeFac .attach(bridgeProxy.address) - .connect(dummyRollup) + .connect(rollupOwner) const sequencerInbox = await sequencerInboxFac .attach(sequencerInboxProxy.address) .connect(user) - const inbox = await inboxFac.attach(inboxProxy.address).connect(user) - - await bridge.initialize(await dummyRollup.getAddress()) + await bridge.initialize(rollup.address) await sequencerInbox.initialize(bridgeProxy.address, { delayBlocks: maxDelayBlocks, @@ -270,11 +283,26 @@ describe('SequencerInboxForceInclude', async () => { futureBlocks: 10, futureSeconds: 3000, }) + + await ( + await sequencerInbox + .connect(rollupOwner) + .setIsBatchPoster(await batchPoster.getAddress(), true) + ).wait() + + const inbox = await inboxFac.attach(inboxProxy.address).connect(user) + await inbox.initialize(bridgeProxy.address, sequencerInbox.address) await bridgeAdmin.setDelayedInbox(inbox.address, true) await bridgeAdmin.setSequencerInbox(sequencerInbox.address) + await ( + await sequencerInbox + .connect(rollupOwner) + .setIsBatchPoster(await batchPoster.getAddress(), true) + ).wait() + const messageTester = (await ( await ethers.getContractFactory('MessageTester') ).deploy()) as MessageTester @@ -283,18 +311,103 @@ describe('SequencerInboxForceInclude', async () => { user, bridge: bridge, inbox: inbox, - sequencerInbox: sequencerInbox, + sequencerInbox: sequencerInbox as SequencerInbox, messageTester, inboxProxy, inboxTemplate, + batchPoster, bridgeProxy, + rollup, + rollupOwner, } } + it('can add batch', async () => { + const { user, inbox, bridge, messageTester, sequencerInbox, batchPoster } = + await setupSequencerInbox() + + await sendDelayedTx( + user, + inbox, + bridge, + messageTester, + 1000000, + 21000000000, + 0, + await user.getAddress(), + BigNumber.from(10), + '0x1010' + ) + + const messagesRead = await bridge.delayedMessageCount() + const seqReportedMessageSubCount = + await bridge.sequencerReportedSubMessageCount() + await ( + await sequencerInbox + .connect(batchPoster) + .functions[ + 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' + ]( + 0, + data, + messagesRead, + ethers.constants.AddressZero, + seqReportedMessageSubCount, + seqReportedMessageSubCount.add(10), + { gasLimit: 10000000 } + ) + ).wait() + }) + it('can force-include', async () => { const { user, inbox, bridge, messageTester, sequencerInbox } = await setupSequencerInbox() + const delayedTx = await sendDelayedTx( + user, + inbox, + bridge, + messageTester, + 1000000, + 21000000000, + 0, + await user.getAddress(), + BigNumber.from(10), + '0x1010' + ) + + const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() + await mineBlocks(delayBlocks.toNumber()) + + await forceIncludeMessages( + sequencerInbox, + delayedTx.inboxAccountLength, + delayedTx.deliveredMessageEvent.kind, + delayedTx.l1BlockNumber, + delayedTx.l1BlockTimestamp, + delayedTx.baseFeeL1, + delayedTx.senderAddr, + delayedTx.deliveredMessageEvent.messageDataHash + ) + }) + + it('can force-include-with-max-seqReportedCount', async () => { + const { user, inbox, bridge, messageTester, batchPoster, sequencerInbox } = + await setupSequencerInbox() + + await sequencerInbox + .connect(batchPoster) + [ + 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' + ]( + 0, + '0x', + 0, + ethers.constants.AddressZero, + 0, + ethers.constants.MaxUint256 + ) + const delayedTx = await sendDelayedTx( user, inbox, @@ -309,7 +422,7 @@ describe('SequencerInboxForceInclude', async () => { ) const maxTimeVariation = await sequencerInbox.maxTimeVariation() - await mineBlocks(maxTimeVariation.delayBlocks.toNumber()) + await mineBlocks(maxTimeVariation[0].toNumber()) await forceIncludeMessages( sequencerInbox, @@ -352,8 +465,8 @@ describe('SequencerInboxForceInclude', async () => { '0xdeadface' ) - const maxTimeVariation = await sequencerInbox.maxTimeVariation() - await mineBlocks(maxTimeVariation.delayBlocks.toNumber()) + const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() + await mineBlocks(delayBlocks.toNumber()) await forceIncludeMessages( sequencerInbox, @@ -417,8 +530,8 @@ describe('SequencerInboxForceInclude', async () => { '0x10101010' ) - const maxTimeVariation = await sequencerInbox.maxTimeVariation() - await mineBlocks(maxTimeVariation.delayBlocks.toNumber()) + const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() + await mineBlocks(delayBlocks.toNumber()) await forceIncludeMessages( sequencerInbox, @@ -448,8 +561,8 @@ describe('SequencerInboxForceInclude', async () => { '0x1010' ) - const maxTimeVariation = await sequencerInbox.maxTimeVariation() - await mineBlocks(maxTimeVariation.delayBlocks.toNumber() - 1, 5) + const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() + await mineBlocks(delayBlocks.toNumber() - 1, 5) await forceIncludeMessages( sequencerInbox, @@ -480,10 +593,10 @@ describe('SequencerInboxForceInclude', async () => { '0x1010' ) - const maxTimeVariation = await sequencerInbox.maxTimeVariation() + const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() // mine a lot of blocks - but use a short time per block // this should mean enough blocks have passed, but not enough time - await mineBlocks(maxTimeVariation.delayBlocks.toNumber() + 1, 5) + await mineBlocks(delayBlocks.toNumber() + 1, 5) await forceIncludeMessages( sequencerInbox, diff --git a/test/contract/toolkit4844.ts b/test/contract/toolkit4844.ts new file mode 100644 index 000000000..258a2400c --- /dev/null +++ b/test/contract/toolkit4844.ts @@ -0,0 +1,136 @@ +import { execSync } from 'child_process' +import { ContractFactory, Signer, ethers } from 'ethers' +import * as http from 'http' +import { IReader4844, IReader4844__factory } from '../../build/types' +import { JsonRpcProvider } from '@ethersproject/providers' +import { bytecode as Reader4844Bytecode } from '../../out/yul/Reader4844.yul/Reader4844.json' + +const wait = async (ms: number) => + new Promise(res => { + setTimeout(res, ms) + }) + +export class Toolkit4844 { + public static DATA_BLOB_HEADER_FLAG = '0x50' // 0x40 | 0x10 + + public static postDataToGeth(body: any): Promise { + return new Promise((resolve, reject) => { + const options = { + hostname: '127.0.0.1', + port: 8545, + path: '/', + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + 'Content-Length': Buffer.byteLength(JSON.stringify(body)), + }, + } + + const req = http.request(options, res => { + let data = '' + + // Event emitted when a chunk of data is received + res.on('data', chunk => { + data += chunk + }) + + // Event emitted when the response is fully received + res.on('end', () => { + resolve(JSON.parse(data)) + }) + }) + + // Handle any errors + req.on('error', error => { + reject(error) + }) + + // Send the POST data + req.write(JSON.stringify(body)) + + // Close the request + req.end() + }) + } + + public static async getTx( + txHash: string + ): Promise { + const body = { + method: 'eth_getTransactionByHash', + params: [txHash], + id: Date.now(), + jsonrpc: '2.0', + } + return (await this.postDataToGeth(body))['result'] + } + + public static async getTxReceipt( + txHash: string + ): Promise { + const body = { + method: 'eth_getTransactionReceipt', + params: [txHash], + id: Date.now(), + jsonrpc: '2.0', + } + return (await this.postDataToGeth(body))['result'] + } + + public static async chainId(): Promise { + const body = { + method: 'eth_chainId', + params: [], + id: Date.now(), + jsonrpc: '2.0', + } + return (await this.postDataToGeth(body))['result'] + } + + public static isReplacementError(err: string) { + const errRegex = + /Error while sending transaction: replacement transaction underpriced:/ + const match = err.match(errRegex) + return Boolean(match) + } + + public static async waitUntilBlockMined( + blockNumber: number, + provider: JsonRpcProvider + ) { + while ((await provider.getBlockNumber()) <= blockNumber) { + await wait(300) + } + } + + public static async sendBlobTx( + privKey: string, + to: string, + blobs: string[], + data: string + ) { + const blobStr = blobs.reduce((acc, blob) => acc + ' -b ' + blob, '') + const blobCommand = `docker run --network=nitro-testnode_default ethpandaops/goomy-blob@sha256:8fd6dfe19bedf43f485f1d5ef3db0a0af569c1a08eacc117d5c5ba43656989f0 blob-sender -p ${privKey} -r http://geth:8545 -t ${to} -d ${data} --gaslimit 1000000${blobStr} 2>&1` + const res = execSync(blobCommand).toString() + const txHashRegex = /0x[a-fA-F0-9]{64}/ + const match = res.match(txHashRegex) + if (match) { + await wait(10000) + return match[0] + } else { + throw new Error('Error sending blob tx:\n' + res) + } + } + + public static async deployReader4844(wallet: Signer): Promise { + const contractFactory = new ContractFactory( + IReader4844__factory.abi, + Reader4844Bytecode, + wallet + ) + const reader4844 = await contractFactory.deploy() + await reader4844.deployed() + + return IReader4844__factory.connect(reader4844.address, wallet) + } +} diff --git a/test/contract/validatorWallet.spec.ts b/test/contract/validatorWallet.spec.ts index 90e1a5d13..cd98ba282 100644 --- a/test/contract/validatorWallet.spec.ts +++ b/test/contract/validatorWallet.spec.ts @@ -5,6 +5,7 @@ import { ValidatorWalletCreator__factory, ValidatorWallet, RollupMock, + RollupMock__factory, } from '../../build/types' import { initializeAccounts } from './utils' @@ -14,6 +15,7 @@ describe('Validator Wallet', () => { let accounts: Awaited> let owner: ArrayElement let executor: ArrayElement + let rollupOwner: ArrayElement let walletCreator: ValidatorWalletCreator let wallet: ValidatorWallet let rollupMock1: RollupMock @@ -26,8 +28,9 @@ describe('Validator Wallet', () => { walletCreator = await WalletCreator.deploy() await walletCreator.deployed() - owner = await accounts[0] - executor = await accounts[1] + owner = accounts[0] + executor = accounts[1] + rollupOwner = accounts[2] const walletCreationTx = await (await walletCreator.createWallet([])).wait() const events = walletCreationTx.logs @@ -45,9 +48,15 @@ describe('Validator Wallet', () => { await wallet.setExecutor([await executor.getAddress()], [true]) await wallet.transferOwnership(await owner.getAddress()) - const RollupMock = await ethers.getContractFactory('RollupMock') - rollupMock1 = (await RollupMock.deploy()) as RollupMock - rollupMock2 = (await RollupMock.deploy()) as RollupMock + const RollupMock = (await ethers.getContractFactory( + 'RollupMock' + )) as RollupMock__factory + rollupMock1 = (await RollupMock.deploy( + await rollupOwner.getAddress() + )) as RollupMock + rollupMock2 = (await RollupMock.deploy( + await rollupOwner.getAddress() + )) as RollupMock await accounts[0].sendTransaction({ to: wallet.address, diff --git a/test/foundry/AbsInbox.t.sol b/test/foundry/AbsInbox.t.sol index 34eb35f7b..c97bb3ef1 100644 --- a/test/foundry/AbsInbox.t.sol +++ b/test/foundry/AbsInbox.t.sol @@ -63,13 +63,18 @@ abstract contract AbsInboxTest is Test { // mock the owner() call on rollup address mockRollupOwner = address(10_000); vm.mockCall( - rollup, abi.encodeWithSelector(IOwnable.owner.selector), abi.encode(mockRollupOwner) + rollup, + abi.encodeWithSelector(IOwnable.owner.selector), + abi.encode(mockRollupOwner) ); // setAllowList shall revert vm.expectRevert( abi.encodeWithSelector( - NotRollupOrOwner.selector, address(this), rollup, mockRollupOwner + NotRollupOrOwner.selector, + address(this), + rollup, + mockRollupOwner ) ); @@ -126,13 +131,18 @@ abstract contract AbsInboxTest is Test { // mock the owner() call on rollup address mockRollupOwner = address(10_000); vm.mockCall( - rollup, abi.encodeWithSelector(IOwnable.owner.selector), abi.encode(mockRollupOwner) + rollup, + abi.encodeWithSelector(IOwnable.owner.selector), + abi.encode(mockRollupOwner) ); // setAllowListEnabled shall revert vm.expectRevert( abi.encodeWithSelector( - NotRollupOrOwner.selector, address(this), rollup, mockRollupOwner + NotRollupOrOwner.selector, + address(this), + rollup, + mockRollupOwner ) ); @@ -141,7 +151,9 @@ abstract contract AbsInboxTest is Test { function test_pause() public { assertEq( - (PausableUpgradeable(address(inbox))).paused(), false, "Invalid initial paused state" + (PausableUpgradeable(address(inbox))).paused(), + false, + "Invalid initial paused state" ); vm.prank(rollup); @@ -154,7 +166,9 @@ abstract contract AbsInboxTest is Test { vm.prank(rollup); inbox.pause(); assertEq( - (PausableUpgradeable(address(inbox))).paused(), true, "Invalid initial paused state" + (PausableUpgradeable(address(inbox))).paused(), + true, + "Invalid initial paused state" ); vm.prank(rollup); inbox.unpause(); @@ -287,8 +301,14 @@ abstract contract AbsInboxTest is Test { // send TX vm.prank(user, user); - uint256 msgNum = - inbox.sendUnsignedTransaction(gasLimit, maxFeePerGas, nonce, user, value, data); + uint256 msgNum = inbox.sendUnsignedTransaction( + gasLimit, + maxFeePerGas, + nonce, + user, + value, + data + ); //// checks assertEq(msgNum, 0, "Invalid msgNum"); diff --git a/test/foundry/BridgeCreator.t.sol b/test/foundry/BridgeCreator.t.sol index 16099869b..f6178d769 100644 --- a/test/foundry/BridgeCreator.t.sol +++ b/test/foundry/BridgeCreator.t.sol @@ -13,11 +13,12 @@ contract BridgeCreatorTest is Test { BridgeCreator public creator; address public owner = address(100); uint256 public constant MAX_DATA_SIZE = 117_964; + IReader4844 dummyReader4844 = IReader4844(address(137)); BridgeCreator.BridgeContracts ethBasedTemplates = BridgeCreator.BridgeContracts({ bridge: new Bridge(), - sequencerInbox: new SequencerInbox(MAX_DATA_SIZE), + sequencerInbox: new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, false), inbox: new Inbox(MAX_DATA_SIZE), rollupEventInbox: new RollupEventInbox(), outbox: new Outbox() @@ -25,7 +26,7 @@ contract BridgeCreatorTest is Test { BridgeCreator.BridgeContracts erc20BasedTemplates = BridgeCreator.BridgeContracts({ bridge: new ERC20Bridge(), - sequencerInbox: ethBasedTemplates.sequencerInbox, + sequencerInbox: new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, true), inbox: new ERC20Inbox(MAX_DATA_SIZE), rollupEventInbox: new ERC20RollupEventInbox(), outbox: new ERC20Outbox() @@ -36,7 +37,7 @@ contract BridgeCreatorTest is Test { creator = new BridgeCreator(ethBasedTemplates, erc20BasedTemplates); } - function getEthBasedTemplates() internal returns (BridgeCreator.BridgeContracts memory) { + function getEthBasedTemplates() internal view returns (BridgeCreator.BridgeContracts memory) { BridgeCreator.BridgeContracts memory templates; ( templates.bridge, @@ -48,7 +49,7 @@ contract BridgeCreatorTest is Test { return templates; } - function getErc20BasedTemplates() internal returns (BridgeCreator.BridgeContracts memory) { + function getErc20BasedTemplates() internal view returns (BridgeCreator.BridgeContracts memory) { BridgeCreator.BridgeContracts memory templates; ( templates.bridge, @@ -192,7 +193,7 @@ contract BridgeCreatorTest is Test { 30, 40 ); - timeVars.delayBlocks; + timeVars.delayBlocks; // TODO: what is this? BridgeCreator.BridgeContracts memory contracts = creator.createBridge( proxyAdmin, diff --git a/test/foundry/ChallengeManager.t.sol b/test/foundry/ChallengeManager.t.sol new file mode 100644 index 000000000..0a46b8af6 --- /dev/null +++ b/test/foundry/ChallengeManager.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +import "forge-std/Test.sol"; +import "./util/TestUtil.sol"; +import "../../src/challenge/ChallengeManager.sol"; + +contract ChallengeManagerTest is Test { + IChallengeResultReceiver resultReceiver = IChallengeResultReceiver(address(137)); + ISequencerInbox sequencerInbox = ISequencerInbox(address(138)); + IBridge bridge = IBridge(address(139)); + IOneStepProofEntry osp = IOneStepProofEntry(address(140)); + IOneStepProofEntry newOsp = IOneStepProofEntry(address(141)); + address proxyAdmin = address(141); + ChallengeManager chalmanImpl = new ChallengeManager(); + + function deploy() public returns (ChallengeManager) { + ChallengeManager chalman = ChallengeManager( + address(new TransparentUpgradeableProxy(address(chalmanImpl), proxyAdmin, "")) + ); + chalman.initialize(resultReceiver, sequencerInbox, bridge, osp); + assertEq( + address(chalman.resultReceiver()), + address(resultReceiver), + "Result receiver not set" + ); + assertEq( + address(chalman.sequencerInbox()), + address(sequencerInbox), + "Sequencer inbox not set" + ); + assertEq(address(chalman.bridge()), address(bridge), "Bridge not set"); + assertEq(address(chalman.osp()), address(osp), "OSP not set"); + return chalman; + } + + function testPostUpgradeInit() public { + ChallengeManager chalman = deploy(); + + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(chalman))).upgradeToAndCall( + address(chalmanImpl), + abi.encodeWithSelector(ChallengeManager.postUpgradeInit.selector, newOsp) + ); + + assertEq(address(chalman.osp()), address(newOsp), "New osp not set"); + } + + function testPostUpgradeInitFailsNotAdmin() public { + ChallengeManager chalman = deploy(); + + vm.expectRevert(abi.encodeWithSelector(NotOwner.selector, address(151), proxyAdmin)); + vm.prank(address(151)); + chalman.postUpgradeInit(osp); + } + + function testPostUpgradeInitFailsNotDelCall() public { + vm.expectRevert(bytes("Function must be called through delegatecall")); + vm.prank(proxyAdmin); + chalmanImpl.postUpgradeInit(osp); + } +} diff --git a/test/foundry/ERC20Bridge.t.sol b/test/foundry/ERC20Bridge.t.sol index 71b815ab7..fc1d2acbf 100644 --- a/test/foundry/ERC20Bridge.t.sol +++ b/test/foundry/ERC20Bridge.t.sol @@ -41,7 +41,9 @@ contract ERC20BridgeTest is AbsBridgeTest { /* solhint-disable func-name-mixedcase */ function test_initialize() public { assertEq( - address(erc20Bridge.nativeToken()), address(nativeToken), "Invalid nativeToken ref" + address(erc20Bridge.nativeToken()), + address(nativeToken), + "Invalid nativeToken ref" ); assertEq(address(bridge.rollup()), rollup, "Invalid rollup ref"); assertEq(bridge.activeOutbox(), address(0), "Invalid activeOutbox ref"); @@ -105,7 +107,9 @@ contract ERC20BridgeTest is AbsBridgeTest { //// checks uint256 userNativeTokenBalanceAfter = nativeToken.balanceOf(address(user)); assertEq( - userNativeTokenBalanceAfter, userNativeTokenBalanceBefore, "Invalid user token balance" + userNativeTokenBalanceAfter, + userNativeTokenBalanceBefore, + "Invalid user token balance" ); uint256 bridgeNativeTokenBalanceAfter = nativeToken.balanceOf(address(bridge)); @@ -135,7 +139,9 @@ contract ERC20BridgeTest is AbsBridgeTest { hoax(inbox); vm.expectRevert(); IEthBridge(address(bridge)).enqueueDelayedMessage{value: 0.1 ether}( - kind, user, messageDataHash + kind, + user, + messageDataHash ); } @@ -166,7 +172,7 @@ contract ERC20BridgeTest is AbsBridgeTest { //// execute call vm.prank(outbox); - (bool success,) = bridge.executeCall(user, withdrawalAmount, data); + (bool success, ) = bridge.executeCall(user, withdrawalAmount, data); //// checks assertTrue(success, "Execute call failed"); @@ -212,8 +218,11 @@ contract ERC20BridgeTest is AbsBridgeTest { //// execute call vm.prank(outbox); - (bool success,) = - bridge.executeCall({to: address(vault), value: withdrawalAmount, data: data}); + (bool success, ) = bridge.executeCall({ + to: address(vault), + value: withdrawalAmount, + data: data + }); //// checks assertTrue(success, "Execute call failed"); @@ -259,8 +268,11 @@ contract ERC20BridgeTest is AbsBridgeTest { //// execute call - do call which reverts vm.prank(outbox); - (bool success, bytes memory returnData) = - bridge.executeCall({to: address(vault), value: withdrawalAmount, data: data}); + (bool success, bytes memory returnData) = bridge.executeCall({ + to: address(vault), + value: withdrawalAmount, + data: data + }); //// checks assertEq(success, false, "Execute shall be unsuccessful"); @@ -361,7 +373,9 @@ contract ERC20BridgeTest is AbsBridgeTest { address to = _gateway; uint256 withdrawAmount = 25 ether; bytes memory data = abi.encodeWithSelector( - MockGateway.withdraw.selector, MockBridgedToken(_nativeToken), withdrawAmount + MockGateway.withdraw.selector, + MockBridgedToken(_nativeToken), + withdrawAmount ); vm.expectRevert(abi.encodeWithSelector(CallNotAllowed.selector)); vm.prank(_outbox); @@ -376,6 +390,7 @@ contract MockBridgedToken is ERC20 { gateway = _gateway; _mint(msg.sender, 1_000_000 ether); } + function bridgeBurn(address account, uint256 amount) external { require(msg.sender == gateway, "ONLY_GATEWAY"); _burn(account, amount); diff --git a/test/foundry/ERC20Inbox.t.sol b/test/foundry/ERC20Inbox.t.sol index 44948af2e..a4680eda9 100644 --- a/test/foundry/ERC20Inbox.t.sol +++ b/test/foundry/ERC20Inbox.t.sol @@ -128,7 +128,8 @@ contract ERC20InboxTest is AbsInboxTest { // expect event vm.expectEmit(true, true, true, true); emit InboxMessageDelivered( - 0, abi.encodePacked(AddressAliasHelper.applyL1ToL2Alias(user), depositAmount) + 0, + abi.encodePacked(AddressAliasHelper.applyL1ToL2Alias(user), depositAmount) ); // deposit tokens -> tx.origin != msg.sender diff --git a/test/foundry/RollupCreator.t.sol b/test/foundry/RollupCreator.t.sol index 6eb500655..68b8ebe24 100644 --- a/test/foundry/RollupCreator.t.sol +++ b/test/foundry/RollupCreator.t.sol @@ -27,25 +27,28 @@ contract RollupCreatorTest is Test { IRollupAdmin public rollupAdmin; IRollupUser public rollupUser; DeployHelper public deployHelper; + IReader4844 dummyReader4844 = IReader4844(address(137)); // 1 gwei uint256 public constant MAX_FEE_PER_GAS = 1_000_000_000; uint256 public constant MAX_DATA_SIZE = 117_964; - BridgeCreator.BridgeContracts public ethBasedTemplates = BridgeCreator.BridgeContracts({ - bridge: new Bridge(), - sequencerInbox: new SequencerInbox(MAX_DATA_SIZE), - inbox: new Inbox(MAX_DATA_SIZE), - rollupEventInbox: new RollupEventInbox(), - outbox: new Outbox() - }); - BridgeCreator.BridgeContracts public erc20BasedTemplates = BridgeCreator.BridgeContracts({ - bridge: new ERC20Bridge(), - sequencerInbox: ethBasedTemplates.sequencerInbox, - inbox: new ERC20Inbox(MAX_DATA_SIZE), - rollupEventInbox: new ERC20RollupEventInbox(), - outbox: new ERC20Outbox() - }); + BridgeCreator.BridgeContracts public ethBasedTemplates = + BridgeCreator.BridgeContracts({ + bridge: new Bridge(), + sequencerInbox: new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, false), + inbox: new Inbox(MAX_DATA_SIZE), + rollupEventInbox: new RollupEventInbox(), + outbox: new Outbox() + }); + BridgeCreator.BridgeContracts public erc20BasedTemplates = + BridgeCreator.BridgeContracts({ + bridge: new ERC20Bridge(), + sequencerInbox: new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, true), + inbox: new ERC20Inbox(MAX_DATA_SIZE), + rollupEventInbox: new ERC20RollupEventInbox(), + outbox: new ERC20Outbox() + }); /* solhint-disable func-name-mixedcase */ function setUp() public { @@ -89,8 +92,12 @@ contract RollupCreatorTest is Test { vm.startPrank(deployer); // deployment params - ISequencerInbox.MaxTimeVariation memory timeVars = - ISequencerInbox.MaxTimeVariation(((60 * 60 * 24) / 15), 12, 60 * 60 * 24, 60 * 60); + ISequencerInbox.MaxTimeVariation memory timeVars = ISequencerInbox.MaxTimeVariation( + ((60 * 60 * 24) / 15), + 12, + 60 * 60 * 24, + 60 * 60 + ); Config memory config = Config({ confirmPeriodBlocks: 20, extraChallengeTimeBlocks: 200, @@ -111,23 +118,27 @@ contract RollupCreatorTest is Test { uint256 balanceBefore = deployer.balance; /// deploy rollup - address batchPoster = makeAddr("batch poster"); + address[] memory batchPosters = new address[](1); + batchPosters[0] = makeAddr("batch poster 1"); + address batchPosterManager = makeAddr("batch poster manager"); address[] memory validators = new address[](2); validators[0] = makeAddr("validator1"); validators[1] = makeAddr("validator2"); RollupCreator.RollupDeploymentParams memory deployParams = RollupCreator .RollupDeploymentParams({ - config: config, - batchPoster: batchPoster, - validators: validators, - maxDataSize: MAX_DATA_SIZE, - nativeToken: address(0), - deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FEE_PER_GAS - }); - address rollupAddress = - rollupCreator.createRollup{value: factoryDeploymentFunds}(deployParams); + config: config, + batchPosters: batchPosters, + validators: validators, + maxDataSize: MAX_DATA_SIZE, + nativeToken: address(0), + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FEE_PER_GAS, + batchPosterManager: batchPosterManager + }); + address rollupAddress = rollupCreator.createRollup{value: factoryDeploymentFunds}( + deployParams + ); vm.stopPrank(); @@ -150,9 +161,11 @@ contract RollupCreatorTest is Test { assertTrue(address(rollup.challengeManager()) != address(0), "Invalid challengeManager"); assertTrue(rollup.isValidator(validators[0]), "Invalid validator set"); assertTrue(rollup.isValidator(validators[1]), "Invalid validator set"); - assertTrue( - ISequencerInbox(address(rollup.sequencerInbox())).isBatchPoster(batchPoster), - "Invalid batch poster" + assertTrue(rollup.sequencerInbox().isBatchPoster(batchPosters[0]), "Invalid batch poster"); + assertEq( + rollup.sequencerInbox().batchPosterManager(), + batchPosterManager, + "Invalid batch poster manager" ); // check proxy admin for non-rollup contracts @@ -199,7 +212,9 @@ contract RollupCreatorTest is Test { // upgrade executor owns rollup assertEq( - IOwnable(rollupAddress).owner(), upgradeExecutorExpectedAddress, "Invalid rollup owner" + IOwnable(rollupAddress).owner(), + upgradeExecutorExpectedAddress, + "Invalid rollup owner" ); assertEq( _getProxyAdmin(rollupAddress), @@ -208,26 +223,36 @@ contract RollupCreatorTest is Test { ); // check rollupOwner has executor role - AccessControlUpgradeable executor = AccessControlUpgradeable(upgradeExecutorExpectedAddress); + AccessControlUpgradeable executor = AccessControlUpgradeable( + upgradeExecutorExpectedAddress + ); assertTrue( - executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), "Invalid executor role" + executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), + "Invalid executor role" ); // check funds are refunded uint256 balanceAfter = deployer.balance; - uint256 factoryDeploymentCost = - deployHelper.getDeploymentTotalCost(rollup.inbox(), MAX_FEE_PER_GAS); + uint256 factoryDeploymentCost = deployHelper.getDeploymentTotalCost( + rollup.inbox(), + MAX_FEE_PER_GAS + ); assertEq(balanceBefore - balanceAfter, factoryDeploymentCost, "Invalid balance"); } function test_createErc20Rollup() public { vm.startPrank(deployer); - address nativeToken = - address(new ERC20PresetFixedSupply("Appchain Token", "App", 1_000_000 ether, deployer)); + address nativeToken = address( + new ERC20PresetFixedSupply("Appchain Token", "App", 1_000_000 ether, deployer) + ); // deployment params - ISequencerInbox.MaxTimeVariation memory timeVars = - ISequencerInbox.MaxTimeVariation(((60 * 60 * 24) / 15), 12, 60 * 60 * 24, 60 * 60); + ISequencerInbox.MaxTimeVariation memory timeVars = ISequencerInbox.MaxTimeVariation( + ((60 * 60 * 24) / 15), + 12, + 60 * 60 * 24, + 60 * 60 + ); Config memory config = Config({ confirmPeriodBlocks: 20, extraChallengeTimeBlocks: 200, @@ -243,25 +268,30 @@ contract RollupCreatorTest is Test { }); // approve fee token to pay for deployment of L2 factories - uint256 expectedCost = 0.1247 ether + 4 * (1400 * 100_000_000_000 + 100_000 * 1_000_000_000); + uint256 expectedCost = 0.1247 ether + + 4 * + (1400 * 100_000_000_000 + 100_000 * 1_000_000_000); IERC20(nativeToken).approve(address(rollupCreator), expectedCost); /// deploy rollup - address batchPoster = makeAddr("batch poster"); + address[] memory batchPosters = new address[](1); + batchPosters[0] = makeAddr("batch poster 1"); + address batchPosterManager = makeAddr("batch poster manager"); address[] memory validators = new address[](2); validators[0] = makeAddr("validator1"); validators[1] = makeAddr("validator2"); RollupCreator.RollupDeploymentParams memory deployParams = RollupCreator .RollupDeploymentParams({ - config: config, - batchPoster: batchPoster, - validators: validators, - maxDataSize: MAX_DATA_SIZE, - nativeToken: nativeToken, - deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FEE_PER_GAS - }); + config: config, + batchPosters: batchPosters, + validators: validators, + maxDataSize: MAX_DATA_SIZE, + nativeToken: nativeToken, + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FEE_PER_GAS, + batchPosterManager: batchPosterManager + }); address rollupAddress = rollupCreator.createRollup(deployParams); @@ -287,13 +317,21 @@ contract RollupCreatorTest is Test { assertTrue(rollup.isValidator(validators[0]), "Invalid validator set"); assertTrue(rollup.isValidator(validators[1]), "Invalid validator set"); assertTrue( - ISequencerInbox(address(rollup.sequencerInbox())).isBatchPoster(batchPoster), + ISequencerInbox(address(rollup.sequencerInbox())).isBatchPoster(batchPosters[0]), "Invalid batch poster" ); + assertEq( + ISequencerInbox(address(rollup.sequencerInbox())).batchPosterManager(), + batchPosterManager, + "Invalid batch poster manager" + ); + // native token check IBridge bridge = RollupCore(address(rollupAddress)).bridge(); assertEq( - IERC20Bridge(address(bridge)).nativeToken(), nativeToken, "Invalid native token ref" + IERC20Bridge(address(bridge)).nativeToken(), + nativeToken, + "Invalid native token ref" ); // check proxy admin for non-rollup contracts @@ -340,7 +378,9 @@ contract RollupCreatorTest is Test { // upgrade executor owns rollup assertEq( - IOwnable(rollupAddress).owner(), upgradeExecutorExpectedAddress, "Invalid rollup owner" + IOwnable(rollupAddress).owner(), + upgradeExecutorExpectedAddress, + "Invalid rollup owner" ); assertEq( _getProxyAdmin(rollupAddress), @@ -349,9 +389,12 @@ contract RollupCreatorTest is Test { ); // check rollupOwner has executor role - AccessControlUpgradeable executor = AccessControlUpgradeable(upgradeExecutorExpectedAddress); + AccessControlUpgradeable executor = AccessControlUpgradeable( + upgradeExecutorExpectedAddress + ); assertTrue( - executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), "Invalid executor role" + executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), + "Invalid executor role" ); } @@ -359,8 +402,12 @@ contract RollupCreatorTest is Test { vm.startPrank(deployer); // deployment params - ISequencerInbox.MaxTimeVariation memory timeVars = - ISequencerInbox.MaxTimeVariation(((60 * 60 * 24) / 15), 12, 60 * 60 * 24, 60 * 60); + ISequencerInbox.MaxTimeVariation memory timeVars = ISequencerInbox.MaxTimeVariation( + ((60 * 60 * 24) / 15), + 12, + 60 * 60 * 24, + 60 * 60 + ); Config memory config = Config({ confirmPeriodBlocks: 20, extraChallengeTimeBlocks: 200, @@ -380,23 +427,27 @@ contract RollupCreatorTest is Test { vm.deal(deployer, factoryDeploymentFunds); /// deploy rollup - address batchPoster = makeAddr("batch poster"); + address[] memory batchPosters = new address[](1); + batchPosters[0] = makeAddr("batch poster 1"); + address batchPosterManager = makeAddr("batch poster manager"); address[] memory validators = new address[](2); validators[0] = makeAddr("validator1"); validators[1] = makeAddr("validator2"); RollupCreator.RollupDeploymentParams memory deployParams = RollupCreator .RollupDeploymentParams({ - config: config, - batchPoster: batchPoster, - validators: validators, - maxDataSize: MAX_DATA_SIZE, - nativeToken: address(0), - deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FEE_PER_GAS - }); - address rollupAddress = - rollupCreator.createRollup{value: factoryDeploymentFunds}(deployParams); + config: config, + batchPosters: batchPosters, + validators: validators, + maxDataSize: MAX_DATA_SIZE, + nativeToken: address(0), + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FEE_PER_GAS, + batchPosterManager: batchPosterManager + }); + address rollupAddress = rollupCreator.createRollup{value: factoryDeploymentFunds}( + deployParams + ); vm.stopPrank(); @@ -404,12 +455,16 @@ contract RollupCreatorTest is Test { RollupCore rollup = RollupCore(rollupAddress); address inbox = address(rollup.inbox()); address proxyAdmin = computeCreateAddress(address(rollupCreator), 1); - IUpgradeExecutor upgradeExecutor = - IUpgradeExecutor(computeCreateAddress(address(rollupCreator), 4)); + IUpgradeExecutor upgradeExecutor = IUpgradeExecutor( + computeCreateAddress(address(rollupCreator), 4) + ); Dummy newLogicImpl = new Dummy(); bytes memory data = abi.encodeWithSelector( - ProxyUpgradeAction.perform.selector, address(proxyAdmin), inbox, address(newLogicImpl) + ProxyUpgradeAction.perform.selector, + address(proxyAdmin), + inbox, + address(newLogicImpl) ); address upgradeAction = address(new ProxyUpgradeAction()); @@ -461,14 +516,19 @@ contract RollupCreatorTest is Test { } function _getSecondary(address proxy) internal view returns (address) { - bytes32 secondarySlot = - bytes32(uint256(keccak256("eip1967.proxy.implementation.secondary")) - 1); + bytes32 secondarySlot = bytes32( + uint256(keccak256("eip1967.proxy.implementation.secondary")) - 1 + ); return address(uint160(uint256(vm.load(proxy, secondarySlot)))); } } contract ProxyUpgradeAction { - function perform(address admin, address payable target, address newLogic) public payable { + function perform( + address admin, + address payable target, + address newLogic + ) public payable { ProxyAdmin(admin).upgrade(TransparentUpgradeableProxy(target), newLogic); } } diff --git a/test/foundry/SequencerInbox.t.sol b/test/foundry/SequencerInbox.t.sol new file mode 100644 index 000000000..5bad2eb65 --- /dev/null +++ b/test/foundry/SequencerInbox.t.sol @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +import "forge-std/Test.sol"; +import "./util/TestUtil.sol"; +import "../../src/bridge/Bridge.sol"; +import "../../src/bridge/SequencerInbox.sol"; +import {ERC20Bridge} from "../../src/bridge/ERC20Bridge.sol"; +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; + +contract RollupMock { + address public immutable owner; + + constructor(address _owner) { + owner = _owner; + } +} + +contract SequencerInboxTest is Test { + // cannot reference events outside of the original contract until 0.8.21 + // we currently use 0.8.9 + event MessageDelivered( + uint256 indexed messageIndex, + bytes32 indexed beforeInboxAcc, + address inbox, + uint8 kind, + address sender, + bytes32 messageDataHash, + uint256 baseFeeL1, + uint64 timestamp + ); + event InboxMessageDelivered(uint256 indexed messageNum, bytes data); + event SequencerBatchDelivered( + uint256 indexed batchSequenceNumber, + bytes32 indexed beforeAcc, + bytes32 indexed afterAcc, + bytes32 delayedAcc, + uint256 afterDelayedMessagesRead, + IBridge.TimeBounds timeBounds, + IBridge.BatchDataLocation dataLocation + ); + + Random RAND = new Random(); + address rollupOwner = address(137); + uint256 maxDataSize = 10000; + ISequencerInbox.MaxTimeVariation maxTimeVariation = + ISequencerInbox.MaxTimeVariation({ + delayBlocks: 10, + futureBlocks: 10, + delaySeconds: 100, + futureSeconds: 100 + }); + address dummyInbox = address(139); + address proxyAdmin = address(140); + IReader4844 dummyReader4844 = IReader4844(address(137)); + + uint256 public constant MAX_DATA_SIZE = 117964; + + function deployRollup(bool isArbHosted) internal returns (SequencerInbox, Bridge) { + RollupMock rollupMock = new RollupMock(rollupOwner); + Bridge bridgeImpl = new Bridge(); + Bridge bridge = Bridge( + address(new TransparentUpgradeableProxy(address(bridgeImpl), proxyAdmin, "")) + ); + + bridge.initialize(IOwnable(address(rollupMock))); + vm.prank(rollupOwner); + bridge.setDelayedInbox(dummyInbox, true); + + SequencerInbox seqInboxImpl = new SequencerInbox( + maxDataSize, + isArbHosted ? IReader4844(address(0)) : dummyReader4844, + false + ); + SequencerInbox seqInbox = SequencerInbox( + address(new TransparentUpgradeableProxy(address(seqInboxImpl), proxyAdmin, "")) + ); + seqInbox.initialize(bridge, maxTimeVariation); + + vm.prank(rollupOwner); + seqInbox.setIsBatchPoster(tx.origin, true); + + vm.prank(rollupOwner); + bridge.setSequencerInbox(address(seqInbox)); + + return (seqInbox, bridge); + } + + function deployFeeTokenBasedRollup() internal returns (SequencerInbox, ERC20Bridge) { + RollupMock rollupMock = new RollupMock(rollupOwner); + ERC20Bridge bridgeImpl = new ERC20Bridge(); + ERC20Bridge bridge = ERC20Bridge( + address(new TransparentUpgradeableProxy(address(bridgeImpl), proxyAdmin, "")) + ); + address nativeToken = address(new ERC20PresetMinterPauser("Appchain Token", "App")); + + bridge.initialize(IOwnable(address(rollupMock)), nativeToken); + vm.prank(rollupOwner); + bridge.setDelayedInbox(dummyInbox, true); + + /// this will result in 'hostChainIsArbitrum = true' + vm.mockCall( + address(100), + abi.encodeWithSelector(ArbSys.arbOSVersion.selector), + abi.encode(uint256(11)) + ); + SequencerInbox seqInboxImpl = new SequencerInbox( + maxDataSize, + IReader4844(address(0)), + true + ); + SequencerInbox seqInbox = SequencerInbox( + address(new TransparentUpgradeableProxy(address(seqInboxImpl), proxyAdmin, "")) + ); + seqInbox.initialize(bridge, maxTimeVariation); + + vm.prank(rollupOwner); + seqInbox.setIsBatchPoster(tx.origin, true); + + vm.prank(rollupOwner); + bridge.setSequencerInbox(address(seqInbox)); + + return (seqInbox, bridge); + } + + function expectEvents( + IBridge bridge, + SequencerInbox seqInbox, + bytes memory data, + bool hostChainIsArbitrum, + bool isUsingFeeToken + ) internal { + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + IBridge.TimeBounds memory timeBounds; + if (block.timestamp > maxTimeVariation.delaySeconds) { + timeBounds.minTimestamp = uint64(block.timestamp - maxTimeVariation.delaySeconds); + } + timeBounds.maxTimestamp = uint64(block.timestamp + maxTimeVariation.futureSeconds); + if (block.number > maxTimeVariation.delayBlocks) { + timeBounds.minBlockNumber = uint64(block.number - maxTimeVariation.delayBlocks); + } + timeBounds.maxBlockNumber = uint64(block.number + maxTimeVariation.futureBlocks); + bytes32 dataHash = keccak256( + bytes.concat( + abi.encodePacked( + timeBounds.minTimestamp, + timeBounds.maxTimestamp, + timeBounds.minBlockNumber, + timeBounds.maxBlockNumber, + uint64(delayedMessagesRead) + ), + data + ) + ); + + bytes32 beforeAcc = bytes32(0); + bytes32 delayedAcc = bridge.delayedInboxAccs(delayedMessagesRead - 1); + bytes32 afterAcc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc)); + + if (!isUsingFeeToken) { + uint256 expectedReportedExtraGas = 0; + if (hostChainIsArbitrum) { + // set 0.1 gwei basefee + uint256 basefee = 100000000; + vm.fee(basefee); + // 30 gwei TX L1 fees + uint256 l1Fees = 30000000000; + vm.mockCall( + address(0x6c), + abi.encodeWithSignature("getCurrentTxL1GasFees()"), + abi.encode(l1Fees) + ); + expectedReportedExtraGas = l1Fees / basefee; + } + + bytes memory spendingReportMsg = abi.encodePacked( + block.timestamp, + msg.sender, + dataHash, + sequenceNumber, + block.basefee, + uint64(expectedReportedExtraGas) + ); + + // spending report + vm.expectEmit(); + emit MessageDelivered( + delayedMessagesRead, + delayedAcc, + address(seqInbox), + L1MessageType_batchPostingReport, + tx.origin, + keccak256(spendingReportMsg), + block.basefee, + uint64(block.timestamp) + ); + + // spending report event in seq inbox + vm.expectEmit(); + emit InboxMessageDelivered(delayedMessagesRead, spendingReportMsg); + } + + // sequencer batch delivered + vm.expectEmit(); + emit SequencerBatchDelivered( + sequenceNumber, + beforeAcc, + afterAcc, + delayedAcc, + delayedMessagesRead, + timeBounds, + IBridge.BatchDataLocation.TxInput + ); + } + + bytes biggerData = + hex"00a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890"; + + function testAddSequencerL2BatchFromOrigin() public { + (SequencerInbox seqInbox, Bridge bridge) = deployRollup(false); + address delayedInboxSender = address(140); + uint8 delayedInboxKind = 3; + bytes32 messageDataHash = RAND.Bytes32(); + bytes memory data = biggerData; // 00 is BROTLI_MESSAGE_HEADER_FLAG + + vm.prank(dummyInbox); + bridge.enqueueDelayedMessage(delayedInboxKind, delayedInboxSender, messageDataHash); + + uint256 subMessageCount = bridge.sequencerReportedSubMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + + // set 60 gwei basefee + uint256 basefee = 60000000000; + vm.fee(basefee); + expectEvents(bridge, seqInbox, data, false, false); + + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + } + + /* solhint-disable func-name-mixedcase */ + function testConstructor() public { + SequencerInbox seqInboxLogic = new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, false); + assertEq(seqInboxLogic.maxDataSize(), MAX_DATA_SIZE, "Invalid MAX_DATA_SIZE"); + assertEq(seqInboxLogic.isUsingFeeToken(), false, "Invalid isUsingFeeToken"); + + SequencerInbox seqInboxProxy = SequencerInbox(TestUtil.deployProxy(address(seqInboxLogic))); + assertEq(seqInboxProxy.maxDataSize(), MAX_DATA_SIZE, "Invalid MAX_DATA_SIZE"); + assertEq(seqInboxProxy.isUsingFeeToken(), false, "Invalid isUsingFeeToken"); + + SequencerInbox seqInboxLogicFeeToken = new SequencerInbox( + MAX_DATA_SIZE, + dummyReader4844, + true + ); + assertEq(seqInboxLogicFeeToken.maxDataSize(), MAX_DATA_SIZE, "Invalid MAX_DATA_SIZE"); + assertEq(seqInboxLogicFeeToken.isUsingFeeToken(), true, "Invalid isUsingFeeToken"); + + SequencerInbox seqInboxProxyFeeToken = SequencerInbox( + TestUtil.deployProxy(address(seqInboxLogicFeeToken)) + ); + assertEq(seqInboxProxyFeeToken.maxDataSize(), MAX_DATA_SIZE, "Invalid MAX_DATA_SIZE"); + assertEq(seqInboxProxyFeeToken.isUsingFeeToken(), true, "Invalid isUsingFeeToken"); + } + + function testInitialize() public { + Bridge _bridge = Bridge( + address(new TransparentUpgradeableProxy(address(new Bridge()), proxyAdmin, "")) + ); + _bridge.initialize(IOwnable(address(new RollupMock(rollupOwner)))); + + address seqInboxLogic = address(new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, false)); + SequencerInbox seqInboxProxy = SequencerInbox(TestUtil.deployProxy(seqInboxLogic)); + seqInboxProxy.initialize(IBridge(_bridge), maxTimeVariation); + + assertEq(seqInboxProxy.isUsingFeeToken(), false, "Invalid isUsingFeeToken"); + assertEq(address(seqInboxProxy.bridge()), address(_bridge), "Invalid bridge"); + assertEq(address(seqInboxProxy.rollup()), address(_bridge.rollup()), "Invalid rollup"); + } + + function testInitialize_FeeTokenBased() public { + ERC20Bridge _bridge = ERC20Bridge( + address(new TransparentUpgradeableProxy(address(new ERC20Bridge()), proxyAdmin, "")) + ); + address nativeToken = address(new ERC20PresetMinterPauser("Appchain Token", "App")); + _bridge.initialize(IOwnable(address(new RollupMock(rollupOwner))), nativeToken); + + address seqInboxLogic = address(new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, true)); + SequencerInbox seqInboxProxy = SequencerInbox(TestUtil.deployProxy(seqInboxLogic)); + seqInboxProxy.initialize(IBridge(_bridge), maxTimeVariation); + + assertEq(seqInboxProxy.isUsingFeeToken(), true, "Invalid isUsingFeeToken"); + assertEq(address(seqInboxProxy.bridge()), address(_bridge), "Invalid bridge"); + assertEq(address(seqInboxProxy.rollup()), address(_bridge.rollup()), "Invalid rollup"); + } + + function testInitialize_revert_NativeTokenMismatch_EthFeeToken() public { + Bridge _bridge = Bridge( + address(new TransparentUpgradeableProxy(address(new Bridge()), proxyAdmin, "")) + ); + _bridge.initialize(IOwnable(address(new RollupMock(rollupOwner)))); + + address seqInboxLogic = address(new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, true)); + SequencerInbox seqInboxProxy = SequencerInbox(TestUtil.deployProxy(seqInboxLogic)); + + vm.expectRevert(abi.encodeWithSelector(NativeTokenMismatch.selector)); + seqInboxProxy.initialize(IBridge(_bridge), maxTimeVariation); + } + + function testInitialize_revert_NativeTokenMismatch_FeeTokenEth() public { + ERC20Bridge _bridge = ERC20Bridge( + address(new TransparentUpgradeableProxy(address(new ERC20Bridge()), proxyAdmin, "")) + ); + address nativeToken = address(new ERC20PresetMinterPauser("Appchain Token", "App")); + _bridge.initialize(IOwnable(address(new RollupMock(rollupOwner))), nativeToken); + + address seqInboxLogic = address(new SequencerInbox(MAX_DATA_SIZE, dummyReader4844, false)); + SequencerInbox seqInboxProxy = SequencerInbox(TestUtil.deployProxy(seqInboxLogic)); + + vm.expectRevert(abi.encodeWithSelector(NativeTokenMismatch.selector)); + seqInboxProxy.initialize(IBridge(_bridge), maxTimeVariation); + } + + function testAddSequencerL2BatchFromOrigin_ArbitrumHosted() public { + // this will result in 'hostChainIsArbitrum = true' + vm.mockCall( + address(100), + abi.encodeWithSelector(ArbSys.arbOSVersion.selector), + abi.encode(uint256(11)) + ); + (SequencerInbox seqInbox, Bridge bridge) = deployRollup(true); + + address delayedInboxSender = address(140); + uint8 delayedInboxKind = 3; + bytes32 messageDataHash = RAND.Bytes32(); + bytes memory data = hex"00567890"; + + vm.prank(dummyInbox); + bridge.enqueueDelayedMessage(delayedInboxKind, delayedInboxSender, messageDataHash); + + uint256 subMessageCount = bridge.sequencerReportedSubMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + + expectEvents(bridge, seqInbox, data, true, false); + + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + } + + function testAddSequencerL2BatchFromOrigin_ArbitrumHostedFeeTokenBased() public { + (SequencerInbox seqInbox, ERC20Bridge bridge) = deployFeeTokenBasedRollup(); + address delayedInboxSender = address(140); + uint8 delayedInboxKind = 3; + bytes32 messageDataHash = RAND.Bytes32(); + bytes memory data = hex"80567890"; + + vm.prank(dummyInbox); + bridge.enqueueDelayedMessage(delayedInboxKind, delayedInboxSender, messageDataHash, 0); + + uint256 subMessageCount = bridge.sequencerReportedSubMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + + // set 40 gwei basefee + uint256 basefee = 40000000000; + vm.fee(basefee); + + expectEvents(IBridge(address(bridge)), seqInbox, data, true, true); + + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + } + + function testAddSequencerL2BatchFromOriginReverts() public { + (SequencerInbox seqInbox, Bridge bridge) = deployRollup(false); + address delayedInboxSender = address(140); + uint8 delayedInboxKind = 3; + bytes32 messageDataHash = RAND.Bytes32(); + bytes memory data = biggerData; // 00 is BROTLI_MESSAGE_HEADER_FLAG + + vm.prank(dummyInbox); + bridge.enqueueDelayedMessage(delayedInboxKind, delayedInboxSender, messageDataHash); + + uint256 subMessageCount = bridge.sequencerReportedSubMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + + vm.expectRevert(abi.encodeWithSelector(NotOrigin.selector)); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + + vm.prank(rollupOwner); + seqInbox.setIsBatchPoster(tx.origin, false); + + vm.expectRevert(abi.encodeWithSelector(NotBatchPoster.selector)); + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + + vm.prank(rollupOwner); + seqInbox.setIsBatchPoster(tx.origin, true); + + bytes memory bigData = bytes.concat( + seqInbox.BROTLI_MESSAGE_HEADER_FLAG(), + RAND.Bytes(maxDataSize - seqInbox.HEADER_LENGTH()) + ); + vm.expectRevert( + abi.encodeWithSelector( + DataTooLarge.selector, + bigData.length + seqInbox.HEADER_LENGTH(), + maxDataSize + ) + ); + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + bigData, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + + bytes memory authenticatedData = bytes.concat(seqInbox.DATA_BLOB_HEADER_FLAG(), data); + vm.expectRevert(abi.encodeWithSelector(InvalidHeaderFlag.selector, authenticatedData[0])); + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + authenticatedData, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + + vm.expectRevert( + abi.encodeWithSelector(BadSequencerNumber.selector, sequenceNumber, sequenceNumber + 5) + ); + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber + 5, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + } + + function testPostUpgradeInitAlreadyInit() public returns (SequencerInbox, SequencerInbox) { + (SequencerInbox seqInbox, ) = deployRollup(false); + SequencerInbox seqInboxImpl = new SequencerInbox(maxDataSize, dummyReader4844, false); + + vm.expectRevert(abi.encodeWithSelector(AlreadyInit.selector)); + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall( + address(seqInboxImpl), + abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector) + ); + return (seqInbox, seqInboxImpl); + } + + function testPostUpgradeInit( + uint64 delayBlocks, + uint64 futureBlocks, + uint64 delaySeconds, + uint64 futureSeconds + ) public { + vm.assume(delayBlocks != 0 || futureBlocks != 0 || delaySeconds != 0 || futureSeconds != 0); + + (SequencerInbox seqInbox, SequencerInbox seqInboxImpl) = testPostUpgradeInitAlreadyInit(); + + vm.expectRevert(abi.encodeWithSelector(AlreadyInit.selector)); + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall( + address(seqInboxImpl), + abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector) + ); + + vm.store(address(seqInbox), bytes32(uint256(4)), bytes32(uint256(delayBlocks))); // slot 4: delayBlocks + vm.store(address(seqInbox), bytes32(uint256(5)), bytes32(uint256(futureBlocks))); // slot 5: futureBlocks + vm.store(address(seqInbox), bytes32(uint256(6)), bytes32(uint256(delaySeconds))); // slot 6: delaySeconds + vm.store(address(seqInbox), bytes32(uint256(7)), bytes32(uint256(futureSeconds))); // slot 7: futureSeconds + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall( + address(seqInboxImpl), + abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector) + ); + + ( + uint256 delayBlocks_, + uint256 futureBlocks_, + uint256 delaySeconds_, + uint256 futureSeconds_ + ) = seqInbox.maxTimeVariation(); + assertEq(delayBlocks_, delayBlocks); + assertEq(futureBlocks_, futureBlocks); + assertEq(delaySeconds_, delaySeconds); + assertEq(futureSeconds_, futureSeconds); + + vm.expectRevert(abi.encodeWithSelector(AlreadyInit.selector)); + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall( + address(seqInboxImpl), + abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector) + ); + } + + function testPostUpgradeInitBadInit( + uint256 delayBlocks, + uint256 futureBlocks, + uint256 delaySeconds, + uint256 futureSeconds + ) public { + vm.assume(delayBlocks > uint256(type(uint64).max)); + vm.assume(futureBlocks > uint256(type(uint64).max)); + vm.assume(delaySeconds > uint256(type(uint64).max)); + vm.assume(futureSeconds > uint256(type(uint64).max)); + + (SequencerInbox seqInbox, SequencerInbox seqInboxImpl) = testPostUpgradeInitAlreadyInit(); + + vm.store(address(seqInbox), bytes32(uint256(4)), bytes32(delayBlocks)); // slot 4: delayBlocks + vm.store(address(seqInbox), bytes32(uint256(5)), bytes32(futureBlocks)); // slot 5: futureBlocks + vm.store(address(seqInbox), bytes32(uint256(6)), bytes32(delaySeconds)); // slot 6: delaySeconds + vm.store(address(seqInbox), bytes32(uint256(7)), bytes32(futureSeconds)); // slot 7: futureSeconds + vm.expectRevert(abi.encodeWithSelector(BadPostUpgradeInit.selector)); + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall( + address(seqInboxImpl), + abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector) + ); + } +} diff --git a/test/foundry/util/TestUtil.sol b/test/foundry/util/TestUtil.sol index 27b326586..def8120c0 100644 --- a/test/foundry/util/TestUtil.sol +++ b/test/foundry/util/TestUtil.sol @@ -10,3 +10,24 @@ library TestUtil { return address(new TransparentUpgradeableProxy(address(logic), address(pa), "")); } } + +contract Random { + bytes32 seed = bytes32(uint256(0x137)); + + function Bytes32() public returns (bytes32) { + seed = keccak256(abi.encodePacked(seed)); + return seed; + } + + function Bytes(uint256 length) public returns (bytes memory) { + require(length > 0, "Length must be greater than 0"); + bytes memory randomBytes = new bytes(length); + + for (uint256 i = 0; i < length; i++) { + Bytes32(); + randomBytes[i] = bytes1(uint8(uint256(seed) % 256)); + } + + return randomBytes; + } +} diff --git a/test/storage/SequencerInbox b/test/storage/SequencerInbox index 252f7297f..54db54859 100644 --- a/test/storage/SequencerInbox +++ b/test/storage/SequencerInbox @@ -1,9 +1,14 @@ -| Name | Type | Slot | Offset | Bytes | Contract | -|--------------------------|----------------------------------------------------------|------|--------|-------|----------------------------------------------| -| totalDelayedMessagesRead | uint256 | 0 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| bridge | contract IBridge | 1 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | -| rollup | contract IOwnable | 2 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | -| isBatchPoster | mapping(address => bool) | 3 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| maxTimeVariation | struct ISequencerInbox.MaxTimeVariation | 4 | 0 | 128 | src/bridge/SequencerInbox.sol:SequencerInbox | -| dasKeySetInfo | mapping(bytes32 => struct ISequencerInbox.DasKeySetInfo) | 8 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| isSequencer | mapping(address => bool) | 9 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| Name | Type | Slot | Offset | Bytes | Contract | +|-----------------------------|----------------------------------------------------------|------|--------|-------|----------------------------------------------| +| totalDelayedMessagesRead | uint256 | 0 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| bridge | contract IBridge | 1 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | +| rollup | contract IOwnable | 2 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | +| isBatchPoster | mapping(address => bool) | 3 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| __LEGACY_MAX_TIME_VARIATION | uint256[4] | 4 | 0 | 128 | src/bridge/SequencerInbox.sol:SequencerInbox | +| dasKeySetInfo | mapping(bytes32 => struct ISequencerInbox.DasKeySetInfo) | 8 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| isSequencer | mapping(address => bool) | 9 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| delayBlocks | uint64 | 10 | 0 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | +| futureBlocks | uint64 | 10 | 8 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | +| delaySeconds | uint64 | 10 | 16 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | +| futureSeconds | uint64 | 10 | 24 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | +| batchPosterManager | address | 11 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | diff --git a/yarn.lock b/yarn.lock index 88eb7328d..66df356d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -965,6 +965,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@hyperapp/router@^0.7.1": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@hyperapp/router/-/router-0.7.2.tgz#6a5edbeceb65bd545d7c55fd59153d7353001c0e" + integrity sha512-RXZhJzx0j1UQK/Ia9j7TQlkH1N6qOvJTTEJCIS9NrM1YJabmXGoI0owkvKxieSgXi6EKQ+wUWhXHj9s+Sy9EPA== + "@metamask/eth-sig-util@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.0.tgz#11553ba06de0d1352332c1bde28c8edd00e0dcf6" @@ -1224,6 +1229,15 @@ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1" "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1" +"@nomiclabs/hardhat-docker@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-docker/-/hardhat-docker-2.0.2.tgz#ae964be17951275a55859ff7358e9e7c77448846" + integrity sha512-XgGEpRT3wlA1VslyB57zyAHV+oll8KnV1TjwnxxC1tpAL04/lbdwpdO5KxInVN8irMSepqFpsiSkqlcnvbE7Ng== + dependencies: + dockerode "^2.5.8" + fs-extra "^7.0.1" + node-fetch "^2.6.0" + "@nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers@^0.3.0-beta.13": version "0.3.0-beta.13" resolved "https://registry.yarnpkg.com/hardhat-deploy-ethers/-/hardhat-deploy-ethers-0.3.0-beta.13.tgz#b96086ff768ddf69928984d5eb0a8d78cfca9366" @@ -1456,6 +1470,18 @@ dependencies: defer-to-connect "^1.0.1" +"@tovarishfin/hardhat-yul@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@tovarishfin/hardhat-yul/-/hardhat-yul-3.0.5.tgz#84d896edfa95249073e30b0d622733e70318fc2e" + integrity sha512-DR5nqqQiv7f3bN885Kr3Z+J3LgCmYMPexIRGeS4bqPkC73Qt8nSET1NzohdJDw0HtquOPqraqDez050V0YGrfw== + dependencies: + "@nomiclabs/hardhat-docker" "^2.0.0" + fs-extra "^10.1.0" + glob "^8.0.3" + solc "^0.8.17" + solpp "^0.11.5" + yulp "^0.2.3" + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -1771,6 +1797,14 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +JSONStream@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea" + integrity sha512-mn0KSip7N4e0UDPZHnqDsHECo5uGQrixQKnAskOM1BIB8hd7QKbd6il8IPRPudPHOeHiECoCFqhyMaRO9+nWyA== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1981,11 +2015,21 @@ antlr4@4.7.1: resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.7.1.tgz#69984014f096e9e775f53dd9744bf994d8959773" integrity sha512-haHyTW7Y9joE5MVs37P2lNYfU2RWBLfcRDD8OWldcdZm5TiCE91B5Xl1oWSwiDUSd4rlExpt2pu1fksYQjRBYQ== +antlr4@~4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.8.0.tgz#f938ec171be7fc2855cd3a533e87647185b32b6a" + integrity sha512-en/MxQ4OkPgGJQ3wD/muzj1uDnFSzdFIhc2+c6bHZokWkuBb6RRvFjpWhPxWLbgQvaEzldJZ0GSQpfSAaE3hqg== + antlr4ts@^0.5.0-alpha.4: version "0.5.0-alpha.4" resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@~3.1.1, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -2178,6 +2222,14 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" + integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -2773,6 +2825,13 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bindings@^1.2.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bip39@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.5.0.tgz#51cbd5179460504a63ea3c000db3f787ca051235" @@ -2784,6 +2843,14 @@ bip39@2.5.0: safe-buffer "^5.0.1" unorm "^1.3.3" +bl@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" + integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + blakejs@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" @@ -2794,12 +2861,20 @@ bluebird@^3.5.0, bluebird@^3.5.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bn-str-256@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/bn-str-256/-/bn-str-256-1.9.1.tgz#898cebee70a3edc3968f97b4cebbc4771025aa82" + integrity sha512-u3muv3WO5sYv9nUQsPnDGLg731yNt/MOlKPK5pmBVqClcl7tY97tyfKxw8ed44HVrpi+7dkgJgQpbXP47a3GoQ== + dependencies: + decimal.js-light "^2.5.0" + lodash "^4.17.11" + bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.8.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0, bn.js@^4.8.0: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== @@ -2984,6 +3059,24 @@ bs58check@^2.1.2: create-hash "^1.1.0" safe-buffer "^5.1.2" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -3134,6 +3227,11 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw== + camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -3265,7 +3363,7 @@ chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.4: +chownr@^1.0.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -3356,6 +3454,15 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3479,11 +3586,16 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^2.12.1: +commander@^2.12.1, commander@^2.19.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^8.1.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -3494,7 +3606,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.1, concat-stream@^1.6.0, concat-stream@^1.6.2: +concat-stream@^1.5.1, concat-stream@^1.6.0, concat-stream@^1.6.2, concat-stream@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3639,7 +3751,7 @@ cross-fetch@^2.1.0, cross-fetch@^2.1.1: node-fetch "^2.6.7" whatwg-fetch "^2.0.4" -cross-spawn@^6.0.5: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -3722,7 +3834,14 @@ debug@4, debug@4.3.4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, de dependencies: ms "2.1.2" -debug@^3.1.0: +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^3.1.0, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -3746,6 +3865,11 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== +decimal.js-light@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -3935,6 +4059,30 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + integrity sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ== + +docker-modem@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-1.0.9.tgz#a1f13e50e6afb6cf3431b2d5e7aac589db6aaba8" + integrity sha512-lVjqCSCIAUDZPAZIeyM125HXfNvOmYYInciphNrLrylUtKyW66meAjSPXWchKVzoIYZx69TPnAepVSSkeawoIw== + dependencies: + JSONStream "1.3.2" + debug "^3.2.6" + readable-stream "~1.0.26-4" + split-ca "^1.0.0" + +dockerode@^2.5.8: + version "2.5.8" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-2.5.8.tgz#1b661e36e1e4f860e25f56e0deabe9f87f1d0acc" + integrity sha512-+7iOUYBeDTScmOmQqpUYQaE7F4vvIt6+gIZNHWhqAQEI887tiPFB9OvXI/HzQYqfUNvukMK+9myLW63oTJPZpw== + dependencies: + concat-stream "~1.6.2" + docker-modem "^1.0.8" + tar-fs "~1.16.3" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -4038,7 +4186,7 @@ encoding@^0.1.11: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4812,7 +4960,24 @@ ethereumjs-wallet@0.6.5: utf8 "^3.0.0" uuid "^3.3.2" -ethers@^4.0.40: +ethers-contracts@*: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ethers-contracts/-/ethers-contracts-2.2.1.tgz#e2bf5dd5e157313ba454b50c646c8472fcd0a8b3" + integrity sha512-3fT2gyhoDhqp/bgaBOenmyu74dDGGO9adkBaOtEuNmFq0Yf4nwynYWJv++rDxe6Z5Dl5cBF304GhnJUVFVlfCA== + dependencies: + ethers-utils "^2.1.0" + +ethers-utils@^2.1.0: + version "2.1.11" + resolved "https://registry.yarnpkg.com/ethers-utils/-/ethers-utils-2.1.11.tgz#b27535ca3226118be300211c39c896b1e5e21641" + integrity sha512-BfkGStBmmLjhTldmp5lifiwUeDjx/yowoWfmUnnvPNsix5PFE8IXQdY5VT/Qo1SXMgxzCe8m0Z8ysUw6Q9OmAw== + dependencies: + bn.js "^4.4.0" + hash.js "^1.0.0" + js-sha3 "0.5.7" + xmlhttprequest "1.8.0" + +ethers@^4.0.39, ethers@^4.0.40: version "4.0.49" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" integrity sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg== @@ -4935,6 +5100,62 @@ ethers@^5.5.2: "@ethersproject/web" "5.6.0" "@ethersproject/wordlists" "5.6.0" +ethjs-extras@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ethjs-extras/-/ethjs-extras-0.0.7.tgz#9fb5f7f55952456b6a0836cdeda8bd376981deee" + integrity sha512-1Ml8B6AUVsY+6o1GI861zRiP1KuHnYVu7T7cRVpAAqEqukQsTuTXBbOX1VYPIe5e68+aoqn8fMTGG4FnrYZA+Q== + dependencies: + ethers-contracts "*" + ethjs-provider-http "^0.1.6" + ethjs-provider-signer "^0.1.4" + ethjs-rpc "^0.1.8" + js-sha3 "^0.7.0" + solidity-to-abi "^1.0.4" + +ethjs-format@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/ethjs-format/-/ethjs-format-0.1.8.tgz#925ecdd965ea72a2a2daf2a122e5bf80b5ad522a" + integrity sha512-G9S+H5+XaHYLA54YVB2RxaBBVyFHtMTlnRTsIUp2+rUbrQ0kFRP6hDUz+OzRQY8tYz/A55Klgg9rG49by5rSNw== + dependencies: + bn.js "4.11.6" + ethjs-schema "0.1.4" + ethjs-util "0.1.3" + is-hex-prefixed "1.0.0" + number-to-bn "1.7.0" + strip-hex-prefix "1.0.0" + +ethjs-provider-http@0.1.6, ethjs-provider-http@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz#1ec5d9b4be257ef1d56a500b22a741985e889420" + integrity sha512-y054N5xyyx43KTQjgdkAEj2uEa/flwpENU5ldx/rmA0Q2yy0vyB2lsOIn/7V0uADMc4iRSHZfnFc9b9YS5Qkdw== + dependencies: + xhr2 "0.1.3" + +ethjs-provider-signer@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/ethjs-provider-signer/-/ethjs-provider-signer-0.1.4.tgz#6bd5cb38a8d5b0ddf46ac1e23a60eea1716171ae" + integrity sha512-ExC8h7HnXwiHBEcMtmtpKr7OT/88+GW3Dphw9D36OM3petIr3dpPDCDB1CkDBcy5s4E7Z/irHvDydAA7/MJnug== + dependencies: + ethjs-provider-http "0.1.6" + ethjs-rpc "0.1.2" + +ethjs-rpc@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ethjs-rpc/-/ethjs-rpc-0.1.2.tgz#39a3456b51c59aeeafb5ba556589a59f2da88d26" + integrity sha512-vZ0VlCaafVn1qY6eWB7O/rOLG75wbHLpvjCYnH/OuGCVk0kytdbGi6nm3UyocHTaJLj5g0dM2fLAdwf18G6DYQ== + dependencies: + ethjs-format "0.1.8" + +ethjs-rpc@^0.1.8: + version "0.1.9" + resolved "https://registry.yarnpkg.com/ethjs-rpc/-/ethjs-rpc-0.1.9.tgz#389dcd61be52e72bc47111a75805f8e45882faea" + integrity sha512-KJqT7cgTeCJQ2RY1AlVmTZVnKIUXMPg+niPN5VJKwRSzpjgfr3LTVHlGbkRCqZtOMDi0ogB2vHZaRQiZBXZTUg== + +ethjs-schema@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.1.4.tgz#0323a16333b1ace9a8f1d696a6ee63448fdd455f" + integrity sha512-fB08XJP+G00Qtv37Y8WCHn4LtQeCdmG7icFAl63L0nzT05MvCfey1E1SwjCOmDG6PGDZ9J2e3u4a+79Nb4VOdg== + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -4943,6 +5164,14 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" +ethjs-util@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.3.tgz#dfd5ea4a400dc5e421a889caf47e081ada78bb55" + integrity sha512-QqpX2dsEG2geSMG9dTMJVhfP1kGRdGMNjiHPiTjkju+X5cB0PQIwUzRr5k21pFkgF5zuLccqe83p7Gh5fFM5tQ== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" @@ -4969,6 +5198,19 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -5162,6 +5404,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -5303,6 +5550,13 @@ fmix@^0.1.0: dependencies: imul "^1.0.0" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + follow-redirects@^1.12.1, follow-redirects@^1.14.0: version "1.14.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" @@ -5388,6 +5642,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -5408,6 +5667,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^4.0.2, fs-extra@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -5553,7 +5821,7 @@ get-stream@^3.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= -get-stream@^4.1.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -5668,6 +5936,17 @@ glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -6138,6 +6417,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +hyperapp@1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/hyperapp/-/hyperapp-1.2.9.tgz#a17ec09634968a5fa5f6b7d649e7a03d9680fcf2" + integrity sha512-bIzi12am7pyQ5nc2qnQpN6GWonjdJp+AghY7j9H9L0vccM1OQ3Cqn13cZlmS9KYm91Nf9fwF4KjvbQekFBxHVw== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -6283,6 +6567,11 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + io-ts@1.10.4: version "1.10.4" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" @@ -6349,7 +6638,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.5, is-buffer@~2.0.3: +is-buffer@^2.0.2, is-buffer@^2.0.5, is-buffer@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -6556,10 +6845,10 @@ is-shared-array-buffer@^1.0.1: dependencies: call-bind "^1.0.2" -is-stream@^1.0.0, is-stream@^1.0.1: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" @@ -6669,6 +6958,11 @@ js-sha3@0.8.0, js-sha3@^0.8.0: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== +js-sha3@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.7.0.tgz#0a5c57b36f79882573b2d84051f8bb85dd1bd63a" + integrity sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -6816,6 +7110,11 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + jsonschema@^1.2.4: version "1.4.0" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" @@ -6839,6 +7138,16 @@ keccak@3.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +keccak@^1.0.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-1.4.0.tgz#572f8a6dbee8e7b3aa421550f9e6408ca2186f80" + integrity sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw== + dependencies: + bindings "^1.2.1" + inherits "^2.0.3" + nan "^2.2.1" + safe-buffer "^5.1.0" + keccak@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" @@ -6909,6 +7218,13 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + level-codec@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.2.tgz#fd60df8c64786a80d44e63423096ffead63d8cbc" @@ -7240,6 +7556,13 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -7281,6 +7604,15 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + memdown@^1.0.0: version "1.4.1" resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.4.1.tgz#b4e4e192174664ffbae41361aa500f3119efe215" @@ -7431,6 +7763,11 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -7474,7 +7811,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.1.0: +minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -7639,6 +7976,11 @@ module-error@^1.0.1, module-error@^1.0.2: resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== +moo@^0.5.0, moo@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c" + integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -7713,6 +8055,20 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nan@^2.2.1: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== + nano-json-stream-parser@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" @@ -7750,6 +8106,16 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +nearley@^2.19.0: + version "2.20.1" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474" + integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ== + dependencies: + commander "^2.19.0" + moo "^0.5.0" + railroad-diagrams "^1.0.0" + randexp "0.4.6" + negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -7790,6 +8156,13 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" +node-fetch@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -7849,6 +8222,13 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + dependencies: + path-key "^2.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -7867,10 +8247,10 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.0.0, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-copy@^0.1.0: version "0.1.0" @@ -8031,6 +8411,15 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" +os-locale@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -8046,11 +8435,21 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -8140,6 +8539,11 @@ parse-cache-control@^1.0.1: resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" integrity sha1-juqz5U+laSD+Fro493+iGqzC104= +parse-es6-imports@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-es6-imports/-/parse-es6-imports-1.0.1.tgz#fbfea61afcd94435c7f697fc439616c18853771b" + integrity sha512-WheMSatJ69ItiKNFTYYzYIbntAT4DC0+dM+a64bLQi6dxxqI5elYqJK9oCo5AYlDxeTo/bimmdo5kv4IxJy34A== + parse-headers@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" @@ -8239,7 +8643,7 @@ path-is-inside@^1.0.2: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= -path-key@^2.0.1: +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= @@ -8508,6 +8912,14 @@ pull-window@^2.1.4: dependencies: looper "^2.0.0" +pump@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" + integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -8567,11 +8979,24 @@ queue-microtask@^1.2.2, queue-microtask@^1.2.3: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + integrity sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A== + ramda@^0.27.1: version "0.27.2" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" integrity sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA== +randexp@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -8652,6 +9077,19 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^2.3.0, readable-stream@^2.3.5: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^3.0.6, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -8661,10 +9099,10 @@ readable-stream@^3.0.6, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~1.0.15: +readable-stream@~1.0.15, readable-stream@~1.0.26-4: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== dependencies: core-util-is "~1.0.0" inherits "~2.0.1" @@ -8709,6 +9147,11 @@ regenerate@^1.2.1: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" + integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -8953,6 +9396,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.1.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -9279,7 +9727,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -9361,6 +9809,20 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +solc@0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.5.7.tgz#d84697ac5cc63d9b2139bfb349cec64b64861cdc" + integrity sha512-DaYFzB3AAYjzPtgUl9LenPY2xjI3wG9k8U8T8YE/sXHVIoCirCY5MB6mhcFPgk/VyUtaWZPUCWiYS1E6RSiiqw== + dependencies: + command-exists "^1.2.8" + fs-extra "^0.30.0" + keccak "^1.0.2" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + yargs "^11.0.0" + solc@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" @@ -9401,6 +9863,19 @@ solc@^0.6.3: semver "^5.5.0" tmp "0.0.33" +solc@^0.8.17: + version "0.8.21" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.21.tgz#c3cd505c360ea2fa0eaa5ab574ef96bffb1a2766" + integrity sha512-N55ogy2dkTRwiONbj4e6wMZqUNaLZkiRcjGyeafjLYzo/tf/IvhHY5P5wpe+H3Fubh9idu071i8eOGO31s1ylg== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + solhint-plugin-prettier@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz#e3b22800ba435cd640a9eca805a7f8bc3e3e6a6b" @@ -9527,6 +10002,26 @@ solidity-coverage@^0.8.4: shelljs "^0.8.3" web3-utils "^1.3.6" +solidity-to-abi@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/solidity-to-abi/-/solidity-to-abi-1.0.4.tgz#b5d1c095e7cb8ee402d5f7a49d2c2ff0e0d63a15" + integrity sha512-2WZpksSLXBAGHBJvv/+M9zI/W4OGxHY3Hmgb75ZqO9hd1rcJMJx/u0zD8DKRWNuzKBw1f5J91VmntewGIrgRjg== + +solpp@^0.11.5: + version "0.11.5" + resolved "https://registry.yarnpkg.com/solpp/-/solpp-0.11.5.tgz#e5f38b5acc952e1cc2e3871d490fdbed910938dd" + integrity sha512-LjzCGMrTDXtera2C4mbQGZSpBznP+o3/82L2CneAAMNbm+t4xPsvfrgJkIaY+IZ5YLrB8IXn7cYthwHMKvAWnQ== + dependencies: + antlr4 "~4.8.0" + axios "^0.21.1" + bn-str-256 "^1.9.1" + commander "^2.19.0" + ethereumjs-util "^6.0.0" + lodash "^4.17.11" + mz "^2.7.0" + resolve "^1.10.0" + semver "^5.6.0" + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -9609,6 +10104,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== +split-ca@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -9698,7 +10198,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -9803,6 +10303,11 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" @@ -9950,6 +10455,29 @@ tape@^4.6.3: string.prototype.trim "~1.2.5" through "~2.3.8" +tar-fs@~1.16.3: + version "1.16.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" + integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== + dependencies: + chownr "^1.0.1" + mkdirp "^0.5.1" + pump "^1.0.0" + tar-stream "^1.1.2" + +tar-stream@^1.1.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" + integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== + dependencies: + bl "^1.0.0" + buffer-alloc "^1.2.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.1" + xtend "^4.0.0" + tar@^4.0.2: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" @@ -9998,6 +10526,20 @@ then-request@^6.0.0: promise "^8.0.0" qs "^6.4.0" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + through2@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -10006,10 +10548,10 @@ through2@^2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.6, through@~2.3.4, through@~2.3.8: +"through@>=2.2.7 <3", through@^2.3.6, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" @@ -10030,6 +10572,11 @@ tmp@0.1.0: dependencies: rimraf "^2.6.3" +to-buffer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -11053,6 +11600,11 @@ xhr2-cookies@1.1.0: dependencies: cookiejar "^2.1.1" +xhr2@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.3.tgz#cbfc4759a69b4a888e78cf4f20b051038757bd11" + integrity sha512-6RmGK22QwC7yXB1CRwyLWuS2opPcKOlAu0ViAnyZjDlzrEmCKL4kLHkfvB8oMRWeztMsNoDGAjsMZY15w/4tTw== + xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: version "2.6.0" resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" @@ -11136,6 +11688,13 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" + integrity sha512-CswCfdOgCr4MMsT1GzbEJ7Z2uYudWyrGX8Bgh/0eyCzj/DXWdKq6a/ADufkzI1WAOIW6jYaXJvRyLhDO0kfqBw== + dependencies: + camelcase "^4.1.0" + yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" @@ -11184,6 +11743,24 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^11.0.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.1.tgz#5052efe3446a4df5ed669c995886cc0f13702766" + integrity sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw== + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^3.1.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^9.0.2" + yargs@^4.7.1: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" @@ -11214,6 +11791,25 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yulp@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/yulp/-/yulp-0.2.3.tgz#f8055cb7784b44a955d76c607f712b5911c701f8" + integrity sha512-2XX6g9hTB5OAMhPYw0Qq9inEmssVFSnq6dZxfkDQyipiZO70NgrrCTcQGt2Dw+jhUCwUkjPKTA6DF7RG7RRpKQ== + dependencies: + bn.js "^5.0.0" + ethers "^4.0.39" + moo "^0.5.1" + nearley "^2.19.0" + parse-es6-imports "^1.0.1" + rfdc "^1.1.4" + optionalDependencies: + "@hyperapp/router" "^0.7.1" + axios "^0.18.1" + ethjs-extras "0.0.7" + hyperapp "1.2.9" + regenerator-runtime "0.13.2" + solc "0.5.7" + zksync-web3@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.3.tgz#64ac2a16d597464c3fc4ae07447a8007631c57c9" diff --git a/yul/Reader4844.yul b/yul/Reader4844.yul new file mode 100644 index 000000000..ef183d793 --- /dev/null +++ b/yul/Reader4844.yul @@ -0,0 +1,39 @@ +object "Reader4844" { + code { + datacopy(0, dataoffset("runtime"), datasize("runtime")) + return(0, datasize("runtime")) + } + object "runtime" { + code { + // This contract does not accept callvalue + if callvalue() { revert(0, 0) } + + // Match against the keccak of the ABI function signature needed. + switch shr(0xe0, calldataload(0)) + // bytes4(keccak("getDataHashes()")) + case 0xe83a2d82 { + let i := 0 + for { } true { } + { + // DATAHASH opcode has hex value 0x49 + let hash := verbatim_1i_1o(hex"49", i) + if iszero(hash) { break } + mstore(add(mul(i, 32), 64), hash) + i := add(i, 1) + } + mstore(0, 32) + mstore(32, i) + return(0, add(mul(i, 32), 64)) + } + // bytes4(keccak("getBlobBaseFee()")) + case 0x1f6d6ef7 { + // BLOBBASEFEE opcode has hex value 0x4a + let blobBasefee := verbatim_0i_1o(hex"4a") + mstore(0, blobBasefee) + return(0, 32) + } + // Unknown selector (revert) + default { revert(0, 0) } + } + } +}