From 686b3b5a6e30c74e3bd664bb80146c9aad05eb13 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 2 Mar 2026 11:31:24 +0100 Subject: [PATCH 01/23] Implement VedaAdapter --- src/helpers/VedaAdapter.sol | 353 ++++++++++ src/helpers/interfaces/IVedaTeller.sol | 114 ++++ test/helpers/VedaLending.t.sol | 889 +++++++++++++++++++++++++ 3 files changed, 1356 insertions(+) create mode 100644 src/helpers/VedaAdapter.sol create mode 100644 src/helpers/interfaces/IVedaTeller.sol create mode 100644 test/helpers/VedaLending.t.sol diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol new file mode 100644 index 00000000..23b4ed1f --- /dev/null +++ b/src/helpers/VedaAdapter.sol @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { Delegation, ModeCode } from "../utils/Types.sol"; +import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; +import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; + +/** + * @title VedaAdapter + * @notice Adapter contract that enables Veda BoringVault deposit and withdrawal operations through MetaMask's + * delegation framework + * @dev This contract acts as an intermediary between users and Veda's BoringVault, enabling delegation-based + * token operations without requiring direct token approvals. + * + * Architecture: + * - BoringVault: The ERC20 vault share token that also custodies assets. On deposit, the vault pulls + * tokens from the caller via `safeTransferFrom`, so this adapter must approve the BoringVault. + * - Teller: The contract that orchestrates deposits/withdrawals. The adapter calls `teller.bulkDeposit()` + * for deposits (requires SOLVER_ROLE) and `teller.withdraw()` for withdrawals (user-facing, no special + * role needed). + * + * Delegation Flow: + * 1. The user creates an initial delegation to an "operator" address (a DeleGator-upgraded account). + * This delegation includes: + * - A transfer enforcer to control which tokens/shares and amounts can be transferred + * - A redeemer enforcer that restricts redemption to only the VedaAdapter contract + * + * 2. The operator then redelegates to this VedaAdapter contract with additional constraints: + * - Allowed methods enforcer limiting which functions can be called + * - Limited calls enforcer restricting the delegation to a single execution + * + * 3. For deposits: the adapter redeems the delegation chain, transfers tokens from the user to itself, + * approves the BoringVault, and calls `teller.bulkDeposit()` to mint shares to the user. + * For withdrawals: the adapter redeems the delegation chain, transfers vault shares from the user + * to itself, and calls `teller.withdraw()` to burn shares and send underlying assets to the user. + * + * Requirements: + * - VedaAdapter must be granted SOLVER_ROLE (or equivalent auth) on the Teller for deposits + * - VedaAdapter must approve the BoringVault to spend deposit tokens + */ +contract VedaAdapter is Ownable2Step { + using SafeERC20 for IERC20; + using ExecutionLib for bytes; + using ModeLib for ModeCode; + + /** + * @notice Parameters for a single deposit operation in a batch + */ + struct DepositParams { + Delegation[] delegations; + address token; + uint256 amount; + uint256 minimumMint; + } + + /** + * @notice Parameters for a single withdrawal operation in a batch + */ + struct WithdrawParams { + Delegation[] delegations; + address token; + uint256 shareAmount; + uint256 minimumAssets; + } + + ////////////////////////////// Events ////////////////////////////// + + /** + * @notice Emitted when a deposit operation is executed via delegation + * @param delegator Address of the token owner (delegator) + * @param delegate Address of the executor (delegate) + * @param token Address of the deposited token + * @param amount Amount of tokens deposited + * @param shares Amount of vault shares minted to the delegator + */ + event DepositExecuted( + address indexed delegator, address indexed delegate, address indexed token, uint256 amount, uint256 shares + ); + + /** + * @notice Emitted when a withdrawal operation is executed via delegation + * @param delegator Address of the share owner (delegator) + * @param delegate Address of the executor (delegate) + * @param token Address of the underlying token withdrawn + * @param shareAmount Amount of vault shares burned + * @param assetsOut Amount of underlying tokens sent to the delegator + */ + event WithdrawExecuted( + address indexed delegator, address indexed delegate, address indexed token, uint256 shareAmount, uint256 assetsOut + ); + + /** + * @notice Emitted when stuck tokens are withdrawn by owner + * @param token Address of the token withdrawn + * @param recipient Address of the recipient + * @param amount Amount of tokens withdrawn + */ + event StuckTokensWithdrawn(IERC20 indexed token, address indexed recipient, uint256 amount); + + ////////////////////////////// Errors ////////////////////////////// + + /// @dev Thrown when a zero address is provided for required parameters + error InvalidZeroAddress(); + + /// @dev Thrown when a zero address is provided for the recipient + error InvalidRecipient(); + + /// @dev Thrown when the delegation chain has fewer than 2 delegations + error InvalidDelegationsLength(); + + /// @dev Thrown when the batch array is empty + error InvalidBatchLength(); + + /// @dev Thrown when msg.sender is not the leaf delegator + error NotLeafDelegator(); + + ////////////////////////////// State ////////////////////////////// + + /** + * @notice The DelegationManager contract used to redeem delegations + */ + IDelegationManager public immutable delegationManager; + + /** + * @notice The BoringVault contract (approval target for token transfers) + */ + address public immutable boringVault; + + /** + * @notice The Teller contract for deposit and withdrawal operations + */ + IVedaTeller public immutable teller; + + ////////////////////////////// Constructor ////////////////////////////// + + /** + * @notice Initializes the adapter with delegation manager, BoringVault, and Teller addresses + * @param _owner Address of the contract owner + * @param _delegationManager Address of the delegation manager contract + * @param _boringVault Address of the BoringVault (token approval target) + * @param _teller Address of the Teller contract (deposit entry point) + */ + constructor(address _owner, address _delegationManager, address _boringVault, address _teller) Ownable(_owner) { + if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0)) { + revert InvalidZeroAddress(); + } + + delegationManager = IDelegationManager(_delegationManager); + boringVault = _boringVault; + teller = IVedaTeller(_teller); + } + + ////////////////////////////// External Methods ////////////////////////////// + + /** + * @notice Deposits tokens into a Veda BoringVault using delegation-based token transfer + * @dev Redeems the delegation to transfer tokens to this adapter, then calls bulkDeposit + * on the Teller which mints vault shares directly to the original token owner. + * Requires at least 2 delegations forming a chain from user to operator to this adapter. + * @param _delegations Array of Delegation objects, sorted leaf to root + * @param _token Address of the token to deposit + * @param _amount Amount of tokens to deposit + * @param _minimumMint Minimum vault shares the user expects to receive (slippage protection) + */ + function depositByDelegation(Delegation[] memory _delegations, address _token, uint256 _amount, uint256 _minimumMint) external { + _executeDepositByDelegation(_delegations, _token, _amount, _minimumMint, msg.sender); + } + + /** + * @notice Deposits tokens using multiple delegation streams, executed sequentially + * @dev Each element is executed one after the other. The caller must be the delegator + * (first delegate in the chain) for each stream. + * @param _depositStreams Array of deposit parameters + */ + function depositByDelegationBatch(DepositParams[] memory _depositStreams) external { + uint256 streamsLength_ = _depositStreams.length; + if (streamsLength_ == 0) revert InvalidBatchLength(); + + address caller_ = msg.sender; + for (uint256 i = 0; i < streamsLength_;) { + DepositParams memory params_ = _depositStreams[i]; + _executeDepositByDelegation(params_.delegations, params_.token, params_.amount, params_.minimumMint, caller_); + unchecked { + ++i; + } + } + } + + /** + * @notice Withdraws underlying tokens from a Veda BoringVault using delegation-based share transfer + * @dev Redeems the delegation to transfer vault shares to this adapter, then calls withdraw + * on the Teller which burns shares and sends underlying assets directly to the original share owner. + * Requires at least 2 delegations forming a chain from user to operator to this adapter. + * @param _delegations Array of Delegation objects, sorted leaf to root + * @param _token Address of the underlying token to receive + * @param _shareAmount Amount of vault shares to redeem + * @param _minimumAssets Minimum underlying assets the user expects to receive (slippage protection) + */ + function withdrawByDelegation( + Delegation[] memory _delegations, + address _token, + uint256 _shareAmount, + uint256 _minimumAssets + ) + external + { + _executeWithdrawByDelegation(_delegations, _token, _shareAmount, _minimumAssets, msg.sender); + } + + /** + * @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially + * @dev Each element is executed one after the other. The caller must be the delegator + * (first delegate in the chain) for each stream. + * @param _withdrawStreams Array of withdraw parameters + */ + function withdrawByDelegationBatch(WithdrawParams[] memory _withdrawStreams) external { + uint256 streamsLength_ = _withdrawStreams.length; + if (streamsLength_ == 0) revert InvalidBatchLength(); + + address caller_ = msg.sender; + for (uint256 i = 0; i < streamsLength_;) { + WithdrawParams memory params_ = _withdrawStreams[i]; + _executeWithdrawByDelegation(params_.delegations, params_.token, params_.shareAmount, params_.minimumAssets, caller_); + unchecked { + ++i; + } + } + } + + /** + * @notice Emergency function to recover tokens accidentally sent to this contract + * @dev This contract should never hold ERC20 tokens as all token operations are handled + * through delegation-based transfers that move tokens directly between users and the BoringVault. + * This function is only for recovering tokens sent to this contract by mistake. + * @param _token The token to be recovered + * @param _amount The amount of tokens to recover + * @param _recipient The address to receive the recovered tokens + */ + function withdrawEmergency(IERC20 _token, uint256 _amount, address _recipient) external onlyOwner { + if (_recipient == address(0)) revert InvalidRecipient(); + + _token.safeTransfer(_recipient, _amount); + + emit StuckTokensWithdrawn(_token, _recipient, _amount); + } + + ////////////////////////////// Private/Internal Methods ////////////////////////////// + + /** + * @notice Ensures sufficient token allowance for BoringVault to pull tokens + * @dev Checks current allowance and sets exact amount if insufficient, avoiding accumulation + * @param _token Token to manage allowance for + * @param _amount Amount needed for the operation + */ + function _ensureAllowance(IERC20 _token, uint256 _amount) private { + uint256 allowance_ = _token.allowance(address(this), boringVault); + if (allowance_ < _amount) { + _token.forceApprove(boringVault, _amount); + } + } + + /** + * @notice Internal implementation of deposit by delegation + * @param _delegations Delegation chain, sorted leaf to root + * @param _token Token to deposit + * @param _amount Amount to deposit + * @param _minimumMint Minimum vault shares expected + * @param _caller Authorized caller (must match leaf delegator) + */ + function _executeDepositByDelegation( + Delegation[] memory _delegations, + address _token, + uint256 _amount, + uint256 _minimumMint, + address _caller + ) + internal + { + uint256 length_ = _delegations.length; + if (length_ < 2) revert InvalidDelegationsLength(); + if (_delegations[0].delegator != _caller) revert NotLeafDelegator(); + if (_token == address(0)) revert InvalidZeroAddress(); + + address rootDelegator_ = _delegations[length_ - 1].delegator; + + // Redeem delegation: transfer tokens from user to this adapter + bytes[] memory permissionContexts_ = new bytes[](1); + permissionContexts_[0] = abi.encode(_delegations); + + ModeCode[] memory encodedModes_ = new ModeCode[](1); + encodedModes_[0] = ModeLib.encodeSimpleSingle(); + + bytes[] memory executionCallDatas_ = new bytes[](1); + bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), _amount)); + executionCallDatas_[0] = ExecutionLib.encodeSingle(_token, 0, encodedTransfer_); + + delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); + + // Approve BoringVault to pull tokens, then deposit via Teller + _ensureAllowance(IERC20(_token), _amount); + uint256 shares_ = teller.bulkDeposit(_token, _amount, _minimumMint, rootDelegator_); + + emit DepositExecuted(rootDelegator_, _caller, _token, _amount, shares_); + } + + /** + * @notice Internal implementation of withdraw by delegation + * @param _delegations Delegation chain, sorted leaf to root + * @param _token Underlying token to receive + * @param _shareAmount Amount of vault shares to redeem + * @param _minimumAssets Minimum underlying assets expected + * @param _caller Authorized caller (must match leaf delegator) + */ + function _executeWithdrawByDelegation( + Delegation[] memory _delegations, + address _token, + uint256 _shareAmount, + uint256 _minimumAssets, + address _caller + ) + internal + { + uint256 length_ = _delegations.length; + if (length_ < 2) revert InvalidDelegationsLength(); + if (_delegations[0].delegator != _caller) revert NotLeafDelegator(); + if (_token == address(0)) revert InvalidZeroAddress(); + + address rootDelegator_ = _delegations[length_ - 1].delegator; + + // Redeem delegation: transfer vault shares from user to this adapter + bytes[] memory permissionContexts_ = new bytes[](1); + permissionContexts_[0] = abi.encode(_delegations); + + ModeCode[] memory encodedModes_ = new ModeCode[](1); + encodedModes_[0] = ModeLib.encodeSimpleSingle(); + + bytes[] memory executionCallDatas_ = new bytes[](1); + bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), _shareAmount)); + executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, encodedTransfer_); + + delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); + + // Withdraw from Teller: burns shares from this adapter, sends underlying to root delegator + uint256 assetsOut_ = teller.withdraw(_token, _shareAmount, _minimumAssets, rootDelegator_); + + emit WithdrawExecuted(rootDelegator_, _caller, _token, _shareAmount, assetsOut_); + } +} diff --git a/src/helpers/interfaces/IVedaTeller.sol b/src/helpers/interfaces/IVedaTeller.sol new file mode 100644 index 00000000..c40daaf5 --- /dev/null +++ b/src/helpers/interfaces/IVedaTeller.sol @@ -0,0 +1,114 @@ +// Based on: +// https://github.com/Se7en-Seas/boring-vault/blob/main/src/base/Roles/TellerWithMultiAssetSupport.sol +// https://github.com/Veda-Labs/boring-vault/blob/dev/oct-2025/src/base/Roles/TellerWithYieldStreaming.sol + +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +/** + * @title IVedaTeller + * @notice Interface for the user-facing functions of Veda's TellerWithMultiAssetSupport. + * @dev Uses `address` for asset parameters to avoid importing Solmate's ERC20. + * The Teller is the entry/exit point for the BoringVault. All functions use `requiresAuth`, + * so callers must be authorized on the Teller's Authority. + */ +interface IVedaTeller { + /** + * @notice Allows users to deposit into the BoringVault, if the contract is not paused. + * @dev Shares are minted to `msg.sender`. A share lock period may apply. + * @param depositAsset The ERC20 token to deposit + * @param depositAmount The amount to deposit + * @param minimumMint The minimum shares the user expects to receive + * @param referralAddress Address used for referral tracking + * @return shares The number of vault shares minted + */ + function deposit( + address depositAsset, + uint256 depositAmount, + uint256 minimumMint, + address referralAddress + ) + external + payable + returns (uint256 shares); + + /** + * @notice Allows users to deposit into the BoringVault using ERC-2612 permit. + * @dev Shares are minted to `msg.sender`. A share lock period may apply. + * @param depositAsset The ERC20 token to deposit + * @param depositAmount The amount to deposit + * @param minimumMint The minimum shares the user expects to receive + * @param deadline The permit deadline timestamp + * @param v The permit signature v value + * @param r The permit signature r value + * @param s The permit signature s value + * @return shares The number of vault shares minted + */ + function depositWithPermit( + address depositAsset, + uint256 depositAmount, + uint256 minimumMint, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + external + returns (uint256 shares); + + /** + * @notice Allows SOLVER_ROLE to deposit on behalf of a recipient. + * @dev Tokens are pulled from `msg.sender`; shares are minted to `to`. + * No share lock period applies to bulk deposits. + * @param depositAsset The ERC20 token to deposit + * @param depositAmount The amount to deposit + * @param minimumMint The minimum shares expected + * @param to The address that will receive the vault shares + * @return shares The number of vault shares minted + */ + function bulkDeposit( + address depositAsset, + uint256 depositAmount, + uint256 minimumMint, + address to + ) + external + returns (uint256 shares); + + /** + * @notice Allows users to withdraw from the BoringVault. + * @dev Available on TellerWithYieldStreaming. Burns shares from `msg.sender` and sends + * underlying assets to `to`. Updates vested yield before withdrawal. + * @param withdrawAsset The ERC20 token to receive + * @param shareAmount The amount of vault shares to burn + * @param minimumAssets The minimum underlying assets expected + * @param to The address that will receive the underlying assets + * @return assetsOut The amount of underlying assets sent + */ + function withdraw( + address withdrawAsset, + uint256 shareAmount, + uint256 minimumAssets, + address to + ) + external + returns (uint256 assetsOut); + + /** + * @notice Allows SOLVER_ROLE to withdraw on behalf of a recipient. + * @dev Shares are burned from `msg.sender`; underlying assets are sent to `to`. + * @param withdrawAsset The ERC20 token to receive + * @param shareAmount The amount of vault shares to burn + * @param minimumAssets The minimum underlying assets expected + * @param to The address that will receive the underlying assets + * @return assetsOut The amount of underlying assets sent + */ + function bulkWithdraw( + address withdrawAsset, + uint256 shareAmount, + uint256 minimumAssets, + address to + ) + external + returns (uint256 assetsOut); +} diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol new file mode 100644 index 00000000..95993cc0 --- /dev/null +++ b/test/helpers/VedaLending.t.sol @@ -0,0 +1,889 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import { Test } from "forge-std/Test.sol"; +import { ModeLib } from "@erc7579/lib/ModeLib.sol"; +import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; +import { IVedaTeller } from "../../src/helpers/interfaces/IVedaTeller.sol"; +import { BaseTest } from "../utils/BaseTest.t.sol"; +import { Implementation, SignatureType } from "../utils/Types.t.sol"; +import { Execution, Delegation, Caveat, ModeCode, CallType, ExecType } from "../../src/utils/Types.sol"; +import { CALLTYPE_BATCH, EXECTYPE_TRY, MODE_DEFAULT } from "../../src/utils/Constants.sol"; +import { ModePayload } from "@erc7579/lib/ModeLib.sol"; +import { AllowedTargetsEnforcer } from "../../src/enforcers/AllowedTargetsEnforcer.sol"; +import { AllowedMethodsEnforcer } from "../../src/enforcers/AllowedMethodsEnforcer.sol"; +import { AllowedCalldataEnforcer } from "../../src/enforcers/AllowedCalldataEnforcer.sol"; +import { RedeemerEnforcer } from "../../src/enforcers/RedeemerEnforcer.sol"; +import { ValueLteEnforcer } from "../../src/enforcers/ValueLteEnforcer.sol"; +import { LimitedCallsEnforcer } from "../../src/enforcers/LimitedCallsEnforcer.sol"; +import { LogicalOrWrapperEnforcer } from "../../src/enforcers/LogicalOrWrapperEnforcer.sol"; +import { ERC20TransferAmountEnforcer } from "../../src/enforcers/ERC20TransferAmountEnforcer.sol"; +import { IDelegationManager } from "../../src/interfaces/IDelegationManager.sol"; +import { VedaAdapter } from "../../src/helpers/VedaAdapter.sol"; +import { BasicERC20 } from "../utils/BasicERC20.t.sol"; + +// @dev Do not remove this comment below +/// forge-config: default.evm_version = "shanghai" + +/** + * @title VedaLending Test + * @notice Tests delegation-based lending on Veda BoringVault. + * @dev Uses a forked Ink mainnet environment to test real contract interactions. + * + * Veda BoringVault implements the ERC-4626 standard for tokenized vaults: + * - Users deposit assets (e.g., USDC) and receive vault shares representing proportional ownership + * - Shares are NOT 1:1 with assets - the conversion rate depends on vault's total assets and total supply + * - The vault contract itself is the ERC-20 share token (no separate token contract) + * - Veda uses multiple contracts to manage the flow of funds: + * - We implement Teller for deposits and withdrawals + * - We implement BoringVault for the approval and custody of assets + * - We modify the mainnet deployment to allow public access to the Teller functions for testing (in production, we would get Solver + * role on Adapter) + * - More docs here: https://docs.veda.tech/architecture-and-flow-of-funds + * + * - Security considerations: + * - We need a redelegation with specific amount to the adapter to prevent over withdrawal or deposit. This would not effect the + * user, but could drain the transaction creator wallet. + */ +contract VedaLendingTest is BaseTest { + using ModeLib for ModeCode; + + // Restricted vault - cannot set on behalfOf + IVedaTeller public constant VEDA_TELLER = IVedaTeller(0xc46f2443b3521632E2E2a903D6da8f965B46f6a0); + IERC20 public constant BORING_VAULT = IERC20(0xDbD87325D7b1189Dcc9255c4926076fF4a96A271); + + address public constant ROLES_AUTHORITY = 0x1F53135155d6fF516bCcfDd9424fcdB8AD1eFB77; + address public constant ROLES_AUTHORITY_OWNER = 0x846abf72fE789cf52FDefB0e924bE9E3670667DA; + + IERC20 public constant USDC = IERC20(0x2D270e6886d130D724215A266106e6832161EAEd); + address public constant USDC_WHALE = 0xd3abC2b515345E47D41C0A1Cd64F8493B80d1ad6; + address public owner; + + // Enforcers for delegation restrictions + AllowedTargetsEnforcer public allowedTargetsEnforcer; + AllowedMethodsEnforcer public allowedMethodsEnforcer; + AllowedCalldataEnforcer public allowedCalldataEnforcer; + ValueLteEnforcer public valueLteEnforcer; + LogicalOrWrapperEnforcer public logicalOrWrapperEnforcer; + ERC20TransferAmountEnforcer public erc20TransferAmountEnforcer; + RedeemerEnforcer public redeemerEnforcer; + LimitedCallsEnforcer public limitedCallsEnforcer; + VedaAdapter public vedaAdapter; + + uint256 public constant MAINNET_FORK_BLOCK = 38688994; // Use latest available block + uint256 public constant INITIAL_USD_BALANCE = 10000000000; // 10k USDC + uint256 public constant DEPOSIT_AMOUNT = 1000000000; // 1k USDC + uint256 public constant SHARE_LOCK_SECONDS = 61; // Warp past the 60s share lock period applied by deposit() + + ////////////////////// Setup ////////////////////// + + function setUp() public override { + // Create fork from mainnet at specific block + vm.createSelectFork(vm.envString("INK_RPC_URL"), MAINNET_FORK_BLOCK); + + // Set implementation type + IMPLEMENTATION = Implementation.Hybrid; + SIGNATURE_TYPE = SignatureType.RawP256; + + // Call parent setup to initialize delegation framework + super.setUp(); + + owner = makeAddr("VedaAdapter Owner"); + + // Deploy enforcers + allowedTargetsEnforcer = new AllowedTargetsEnforcer(); + allowedMethodsEnforcer = new AllowedMethodsEnforcer(); + allowedCalldataEnforcer = new AllowedCalldataEnforcer(); + valueLteEnforcer = new ValueLteEnforcer(); + erc20TransferAmountEnforcer = new ERC20TransferAmountEnforcer(); + redeemerEnforcer = new RedeemerEnforcer(); + limitedCallsEnforcer = new LimitedCallsEnforcer(); + logicalOrWrapperEnforcer = new LogicalOrWrapperEnforcer(delegationManager); + vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + + vm.label(address(allowedTargetsEnforcer), "AllowedTargetsEnforcer"); + vm.label(address(allowedMethodsEnforcer), "AllowedMethodsEnforcer"); + vm.label(address(allowedCalldataEnforcer), "AllowedCalldataEnforcer"); + vm.label(address(valueLteEnforcer), "ValueLteEnforcer"); + vm.label(address(logicalOrWrapperEnforcer), "LogicalOrWrapperEnforcer"); + vm.label(address(erc20TransferAmountEnforcer), "ERC20TransferAmountEnforcer"); + vm.label(address(vedaAdapter), "VedaAdapter"); + vm.label(address(BORING_VAULT), "Veda BoringVault"); + vm.label(address(VEDA_TELLER), "Veda Teller"); + vm.label(address(USDC), "USDC"); + vm.label(USDC_WHALE, "USDC Whale"); + + vm.deal(address(users.alice.deleGator), 1 ether); + vm.deal(address(users.bob.deleGator), 1 ether); + + vm.prank(USDC_WHALE); + USDC.transfer(address(users.alice.deleGator), INITIAL_USD_BALANCE); // 10k USDC + + // Make solver-gated Teller functions publicly callable on the fork + _enableVedaTellerAccess(); + } + + // ================================================================================== + // Section 1: Direct Protocol Tests (Fork Sanity) + // Validates the forked mainnet environment works before testing adapter logic. + // ================================================================================== + + function test_deposit_direct_usdc() public { + uint256 aliceUSDCInitialBalance_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCInitialBalance_, INITIAL_USD_BALANCE); + + uint256 aliceSharesBefore_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + vm.prank(address(users.alice.deleGator)); + USDC.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + vm.prank(address(users.alice.deleGator)); + uint256 sharesMinted_ = VEDA_TELLER.deposit(address(USDC), DEPOSIT_AMOUNT, 0, address(0)); + + uint256 aliceUSDCBalance_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCBalance_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT); + + uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesAfter_ - aliceSharesBefore_, sharesMinted_); + } + + function test_withdraw_direct_usdc() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceUSDCAfterDeposit_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCAfterDeposit_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should have vault shares after deposit"); + + // Withdraw all shares back to USDC + vm.prank(address(users.alice.deleGator)); + BORING_VAULT.approve(address(BORING_VAULT), aliceShares_); + vm.prank(address(users.alice.deleGator)); + uint256 assetsOut_ = VEDA_TELLER.withdraw(address(USDC), aliceShares_, 0, address(users.alice.deleGator)); + + assertGt(assetsOut_, 0, "Should receive assets back"); + + uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesAfter_, 0, "All shares should be burned"); + + uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); + assertApproxEqAbs(aliceUSDCFinal_, INITIAL_USD_BALANCE, DEPOSIT_AMOUNT / 100, "USDC balance should be close to initial"); + } + + // ================================================================================== + // Section 2: Adapter Happy-Path Tests (Core Functionality) + // Validates the standard deposit/withdraw flow via the adapter using delegations. + // ================================================================================== + + function test_deposit_viaAdapterDelegation_usdc() public { + uint256 aliceUSDCInitialBalance_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCInitialBalance_, INITIAL_USD_BALANCE); + uint256 aliceSharesInitial_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesInitial_, 0); + + // Alice delegates USDC transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + + // Bob redelegates to the VedaAdapter with a transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + + uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCFinal_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT, "USDC balance should decrease"); + + uint256 aliceSharesFinal_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceSharesFinal_, 0, "Shares should be minted to Alice"); + } + + function test_withdraw_viaAdapterDelegation_usdc() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should have vault shares"); + uint256 aliceUSDCBefore_ = USDC.balanceOf(address(users.alice.deleGator)); + + // Alice delegates BoringVault share transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + + // Bob redelegates to the VedaAdapter with a share transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), aliceShares_, 0); + + uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertEq(aliceSharesAfter_, 0, "All shares should be burned"); + + uint256 aliceUSDCAfter_ = USDC.balanceOf(address(users.alice.deleGator)); + assertGt(aliceUSDCAfter_, aliceUSDCBefore_, "Alice should receive USDC back"); + assertApproxEqAbs(aliceUSDCAfter_, INITIAL_USD_BALANCE, DEPOSIT_AMOUNT / 100, "USDC balance should be close to initial"); + } + + // ================================================================================== + // Section 3: Constructor Validation Tests + // Ensures the adapter rejects invalid constructor parameters. + // ================================================================================== + + /// @notice Constructor must revert when delegationManager is zero address + function test_constructor_revertsOnZeroDelegationManager() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER)); + } + + /// @notice Constructor must revert when boringVault is zero address + function test_constructor_revertsOnZeroBoringVault() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER)); + } + + /// @notice Constructor must revert when teller is zero address + function test_constructor_revertsOnZeroTeller() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0)); + } + + /// @notice Constructor must revert when owner is zero address (OZ Ownable) + function test_constructor_revertsOnZeroOwner() public { + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0))); + new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + } + + /// @notice Constructor must store immutable state correctly with valid inputs + function test_constructor_successWithValidAddresses() public { + VedaAdapter newAdapter_ = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + + assertEq(address(newAdapter_.delegationManager()), address(delegationManager)); + assertEq(newAdapter_.boringVault(), address(BORING_VAULT)); + assertEq(address(newAdapter_.teller()), address(VEDA_TELLER)); + assertEq(newAdapter_.owner(), owner); + } + + // ================================================================================== + // Section 4: Deposit Input Validation / Revert Tests + // Ensures depositByDelegation rejects invalid inputs before any state changes. + // ================================================================================== + + /// @notice depositByDelegation must revert with 0 delegations + function test_depositByDelegation_revertsOnEmptyDelegations() public { + Delegation[] memory delegations_ = new Delegation[](0); + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice depositByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) + function test_depositByDelegation_revertsOnSingleDelegation() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), DEPOSIT_AMOUNT); + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice depositByDelegation must revert when msg.sender does not match delegations[0].delegator + function test_depositByDelegation_revertsOnUnauthorizedCaller() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // Alice tries to call but Bob is delegations[0].delegator + vm.expectRevert(VedaAdapter.NotLeafDelegator.selector); + vm.prank(address(users.alice.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice depositByDelegation must revert when token address is zero + function test_depositByDelegation_revertsOnZeroTokenAddress() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), DEPOSIT_AMOUNT); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(0), DEPOSIT_AMOUNT, 0); + } + + /// @notice Depositing more than the delegation's ERC20TransferAmountEnforcer cap must revert + function test_depositByDelegation_revertsOnExcessiveAmount() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + uint256 excessiveAmount_ = DEPOSIT_AMOUNT + 1; + vm.expectRevert(); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), excessiveAmount_, 0); + + // Verify no state change + assertEq(USDC.balanceOf(address(users.alice.deleGator)), INITIAL_USD_BALANCE); + } + + // ================================================================================== + // Section 5: Withdraw Input Validation / Revert Tests + // Ensures withdrawByDelegation rejects invalid inputs before any state changes. + // ================================================================================== + + /// @notice withdrawByDelegation must revert with 0 delegations + function test_withdrawByDelegation_revertsOnEmptyDelegations() public { + _setupLendingState(); + + Delegation[] memory delegations_ = new Delegation[](0); + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice withdrawByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) + function test_withdrawByDelegation_revertsOnSingleDelegation() public { + _setupLendingState(); + + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), DEPOSIT_AMOUNT); + Delegation[] memory delegations_ = new Delegation[](1); + delegations_[0] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice withdrawByDelegation must revert when msg.sender does not match delegations[0].delegator + function test_withdrawByDelegation_revertsOnUnauthorizedCaller() public { + _setupLendingState(); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // Alice tries to call but Bob is delegations[0].delegator + vm.expectRevert(VedaAdapter.NotLeafDelegator.selector); + vm.prank(address(users.alice.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice withdrawByDelegation must revert when token address is zero + function test_withdrawByDelegation_revertsOnZeroTokenAddress() public { + _setupLendingState(); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(0), DEPOSIT_AMOUNT, 0); + } + + // ================================================================================== + // Section 6: Event Emission Tests + // Validates that adapter emits correct events with expected indexed parameters. + // ================================================================================== + + /// @notice depositByDelegation must emit DepositExecuted with correct parameters + function test_depositByDelegation_emitsDepositExecutedEvent() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // Expect event: check indexed delegator, delegate, and token. Amount and shares are checked via topic4. + vm.expectEmit(true, true, true, false, address(vedaAdapter)); + emit VedaAdapter.DepositExecuted( + address(users.alice.deleGator), address(users.bob.deleGator), address(USDC), DEPOSIT_AMOUNT, 0 + ); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + /// @notice withdrawByDelegation must emit WithdrawExecuted with correct parameters + function test_withdrawByDelegation_emitsWithdrawExecutedEvent() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // Expect event: check indexed delegator, delegate, and token. shareAmount and assetsOut are checked via topic4. + vm.expectEmit(true, true, true, false, address(vedaAdapter)); + emit VedaAdapter.WithdrawExecuted( + address(users.alice.deleGator), address(users.bob.deleGator), address(USDC), aliceShares_, 0 + ); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), aliceShares_, 0); + } + + // ================================================================================== + // Section 7: Batch Operation Tests + // Validates depositByDelegationBatch and withdrawByDelegationBatch. + // ================================================================================== + + /// @notice depositByDelegationBatch must revert on empty array + function test_depositByDelegationBatch_revertsOnEmptyArray() public { + VedaAdapter.DepositParams[] memory streams_ = new VedaAdapter.DepositParams[](0); + + vm.expectRevert(VedaAdapter.InvalidBatchLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegationBatch(streams_); + } + + /// @notice withdrawByDelegationBatch must revert on empty array + function test_withdrawByDelegationBatch_revertsOnEmptyArray() public { + VedaAdapter.WithdrawParams[] memory streams_ = new VedaAdapter.WithdrawParams[](0); + + vm.expectRevert(VedaAdapter.InvalidBatchLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegationBatch(streams_); + } + + /// @notice Batch deposit with 2 independent delegation chains in a single transaction + function test_depositByDelegationBatch_twoDelegationChains() public { + uint256 amount1_ = 300 * 1e6; // 300 USDC + uint256 amount2_ = 400 * 1e6; // 400 USDC + + // Chain 1: Alice -> Bob -> VedaAdapter (salt 0) + Delegation memory delegation1_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, 0 + ); + Delegation memory redelegation1_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(delegation1_), address(USDC), amount1_, 0); + Delegation[] memory delegations1_ = new Delegation[](2); + delegations1_[0] = redelegation1_; + delegations1_[1] = delegation1_; + + // Chain 2: Alice -> Bob -> VedaAdapter (salt 1) + Delegation memory delegation2_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, 1 + ); + Delegation memory redelegation2_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(delegation2_), address(USDC), amount2_, 1); + Delegation[] memory delegations2_ = new Delegation[](2); + delegations2_[0] = redelegation2_; + delegations2_[1] = delegation2_; + + VedaAdapter.DepositParams[] memory streams_ = new VedaAdapter.DepositParams[](2); + streams_[0] = + VedaAdapter.DepositParams({ delegations: delegations1_, token: address(USDC), amount: amount1_, minimumMint: 0 }); + streams_[1] = + VedaAdapter.DepositParams({ delegations: delegations2_, token: address(USDC), amount: amount2_, minimumMint: 0 }); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegationBatch(streams_); + + uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); + assertEq(aliceUSDCFinal_, INITIAL_USD_BALANCE - amount1_ - amount2_, "USDC should decrease by total batch amount"); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should receive vault shares from batch deposit"); + } + + /// @notice Batch withdraw with 2 independent delegation chains in a single transaction + function test_withdrawByDelegationBatch_twoDelegationChains() public { + // Setup: Deposit via adapter to create shares (bulkDeposit skips share lock) + _depositViaAdapter(DEPOSIT_AMOUNT, 10); + + uint256 totalShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(totalShares_, 0, "Alice should have shares after deposit"); + + uint256 sharesPart1_ = totalShares_ / 2; + uint256 sharesPart2_ = totalShares_ - sharesPart1_; + + VedaAdapter.WithdrawParams[] memory wdStreams_ = new VedaAdapter.WithdrawParams[](2); + wdStreams_[0] = _buildWithdrawParams(sharesPart1_, 20); + wdStreams_[1] = _buildWithdrawParams(sharesPart2_, 21); + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegationBatch(wdStreams_); + + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0, "All shares should be redeemed after batch withdraw"); + assertApproxEqAbs( + USDC.balanceOf(address(users.alice.deleGator)), + INITIAL_USD_BALANCE, + DEPOSIT_AMOUNT / 100, + "USDC should be approximately restored" + ); + } + + // ================================================================================== + // Section 8: Emergency Withdraw Tests + // Validates the owner-only withdrawEmergency function for recovering stuck tokens. + // ================================================================================== + + /// @notice Only the contract owner can call withdrawEmergency + function test_withdrawEmergency_revertsOnNonOwner() public { + BasicERC20 testToken_ = new BasicERC20(owner, "TestToken", "TST", 0); + vm.prank(owner); + testToken_.mint(address(vedaAdapter), 100 ether); + + vm.prank(address(users.alice.deleGator)); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(users.alice.deleGator))); + vedaAdapter.withdrawEmergency(testToken_, 50 ether, address(users.alice.deleGator)); + + assertEq(testToken_.balanceOf(address(vedaAdapter)), 100 ether, "Balance should be unchanged"); + } + + /// @notice Owner can recover stuck tokens; emits StuckTokensWithdrawn event + function test_withdrawEmergency_recoverTokens() public { + BasicERC20 testToken_ = new BasicERC20(owner, "TestToken", "TST", 0); + vm.prank(owner); + testToken_.mint(address(vedaAdapter), 100 ether); + + vm.expectEmit(true, true, true, true, address(vedaAdapter)); + emit VedaAdapter.StuckTokensWithdrawn(testToken_, address(users.alice.deleGator), 50 ether); + + vm.prank(owner); + vedaAdapter.withdrawEmergency(testToken_, 50 ether, address(users.alice.deleGator)); + + assertEq(testToken_.balanceOf(address(vedaAdapter)), 50 ether, "Adapter should retain remaining tokens"); + assertEq(testToken_.balanceOf(address(users.alice.deleGator)), 50 ether, "Recipient should receive tokens"); + } + + /// @notice withdrawEmergency must revert when recipient is zero address + function test_withdrawEmergency_revertsOnZeroRecipient() public { + BasicERC20 testToken_ = new BasicERC20(owner, "TestToken", "TST", 0); + vm.prank(owner); + testToken_.mint(address(vedaAdapter), 100 ether); + + vm.expectRevert(VedaAdapter.InvalidRecipient.selector); + vm.prank(owner); + vedaAdapter.withdrawEmergency(testToken_, 50 ether, address(0)); + } + + // ================================================================================== + // Section 9: Edge Cases and Security Validation + // Tests for subtle behaviors, allowance management, chain integrity, and token mismatch. + // ================================================================================== + + /// @notice After a deposit, the adapter must not retain any deposited tokens + function test_adapterDoesNotRetainTokensAfterDeposit() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + + assertEq(USDC.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any USDC after deposit"); + } + + /// @notice After a withdraw, the adapter must not retain any vault shares + function test_adapterDoesNotRetainSharesAfterWithdraw() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), aliceShares_, 0); + + assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); + } + + /// @notice BoringVault must fully consume the allowance granted by the adapter during bulkDeposit. + /// Verifies that _ensureAllowance does not cause unbounded allowance accumulation. + function test_allowanceFullyConsumedAfterDeposit() public { + assertEq(USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), 0, "Initial allowance should be 0"); + + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + + assertEq( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + 0, + "Allowance must be fully consumed after bulkDeposit -- no residual accumulation" + ); + } + + /// @notice A 3-level delegation chain (Alice -> Carol -> Bob -> Adapter) must correctly resolve + /// rootDelegator as Alice, ensuring shares are minted to the actual token owner. + function test_depositByDelegation_withThreeLevelDelegationChain() public { + vm.deal(address(users.carol.deleGator), 1 ether); + + // Root delegation: Alice -> Carol (with transfer enforcer + redeemer enforcer) + Delegation memory rootDelegation_ = + _createTransferDelegation(address(users.carol.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + + // Middle delegation: Carol -> Bob (no additional caveats, just extends the chain) + Delegation memory middleDelegation_ = Delegation({ + delegate: address(users.bob.deleGator), + delegator: address(users.carol.deleGator), + authority: EncoderLib._getDelegationHash(rootDelegation_), + caveats: new Caveat[](0), + salt: 0, + signature: hex"" + }); + middleDelegation_ = signDelegation(users.carol, middleDelegation_); + + // Leaf delegation: Bob -> VedaAdapter (with transfer amount cap) + Delegation memory adapterDelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(middleDelegation_), address(USDC), DEPOSIT_AMOUNT); + + // Chain order: [leaf, middle, root] + Delegation[] memory delegations_ = new Delegation[](3); + delegations_[0] = adapterDelegation_; + delegations_[1] = middleDelegation_; + delegations_[2] = rootDelegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + + // rootDelegator_ = delegations[2].delegator = Alice + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Shares must be minted to Alice (root delegator), not Carol or Bob"); + + assertEq(BORING_VAULT.balanceOf(address(users.carol.deleGator)), 0, "Carol must not receive shares"); + assertEq(BORING_VAULT.balanceOf(address(users.bob.deleGator)), 0, "Bob must not receive shares"); + } + + /// @notice Passing a token to depositByDelegation that differs from the delegation enforcer's + /// token must revert, because the transfer calldata won't match the enforcer's terms. + function test_depositByDelegation_revertsOnTokenMismatch() public { + // Delegation enforcer is set up for BORING_VAULT (share token), but we try to deposit USDC + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + // The adapter will try to transfer USDC, but the enforcer only allows BORING_VAULT token transfers + vm.expectRevert(); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + } + + // ================================================================================== + // Helper Functions + // ================================================================================== + + /// @notice Pranks into the RolesAuthority owner to make solver-gated Teller functions publicly callable + function _enableVedaTellerAccess() internal { + IRolesAuthority rolesAuthority_ = IRolesAuthority(ROLES_AUTHORITY); + + vm.prank(ROLES_AUTHORITY_OWNER); + rolesAuthority_.setPublicCapability(address(VEDA_TELLER), IVedaTeller.bulkDeposit.selector, true); + + vm.prank(ROLES_AUTHORITY_OWNER); + rolesAuthority_.setPublicCapability(address(VEDA_TELLER), IVedaTeller.bulkWithdraw.selector, true); + } + + /// @notice Sets up initial lending state (Alice deposits USDC to get vault shares) + function _setupLendingState() internal { + vm.prank(address(users.alice.deleGator)); + USDC.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + vm.prank(address(users.alice.deleGator)); + VEDA_TELLER.deposit(address(USDC), DEPOSIT_AMOUNT, 0, address(0)); + } + + /// @notice Deposits USDC via adapter delegation (helper to reduce stack depth in batch tests) + function _depositViaAdapter(uint256 _amount, uint256 _salt) internal { + Delegation memory delegation_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, _salt + ); + Delegation memory redelegation_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(delegation_), address(USDC), _amount, _salt); + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, address(USDC), _amount, 0); + } + + /// @notice Builds a WithdrawParams struct for batch withdraw (helper to reduce stack depth) + function _buildWithdrawParams(uint256 _shareAmount, uint256 _salt) internal view returns (VedaAdapter.WithdrawParams memory) { + Delegation memory wd_ = _createTransferDelegationWithSalt( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max, _salt + ); + Delegation memory rewd_ = + _createAdapterRedelegationWithSalt(EncoderLib._getDelegationHash(wd_), address(BORING_VAULT), _shareAmount, _salt); + Delegation[] memory wdDelegations_ = new Delegation[](2); + wdDelegations_[0] = rewd_; + wdDelegations_[1] = wd_; + + return VedaAdapter.WithdrawParams({ + delegations: wdDelegations_, token: address(USDC), shareAmount: _shareAmount, minimumAssets: 0 + }); + } + + /// @notice Creates a transfer delegation with ERC20TransferAmountEnforcer and RedeemerEnforcer + function _createTransferDelegation( + address _delegate, + address _redeemer, + address _token, + uint256 _amount + ) + internal + view + returns (Delegation memory) + { + return _createTransferDelegationWithSalt(_delegate, _redeemer, _token, _amount, 0); + } + + /// @notice Creates a transfer delegation with a custom salt for unique delegation hashes in batch operations + function _createTransferDelegationWithSalt( + address _delegate, + address _redeemer, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) + { + Caveat[] memory caveats_ = new Caveat[](2); + caveats_[0] = + Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: abi.encodePacked(_token, _amount) }); + + caveats_[1] = Caveat({ args: hex"", enforcer: address(redeemerEnforcer), terms: abi.encodePacked(_redeemer) }); + + Delegation memory delegation_ = Delegation({ + delegate: _delegate, + delegator: address(users.alice.deleGator), + authority: ROOT_AUTHORITY, + caveats: caveats_, + salt: _salt, + signature: hex"" + }); + + return signDelegation(users.alice, delegation_); + } + + /// @notice Creates an adapter redelegation with ERC20TransferAmountEnforcer + function _createAdapterRedelegation( + bytes32 _authority, + address _token, + uint256 _amount + ) + internal + view + returns (Delegation memory) + { + return _createAdapterRedelegationWithSalt(_authority, _token, _amount, 0); + } + + /// @notice Creates an adapter redelegation with a custom salt for unique delegation hashes in batch operations + function _createAdapterRedelegationWithSalt( + bytes32 _authority, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) + { + Caveat[] memory caveats_ = new Caveat[](1); + caveats_[0] = + Caveat({ args: hex"", enforcer: address(erc20TransferAmountEnforcer), terms: abi.encodePacked(_token, _amount) }); + + Delegation memory delegation_ = Delegation({ + delegate: address(vedaAdapter), + delegator: address(users.bob.deleGator), + authority: _authority, + caveats: caveats_, + salt: _salt, + signature: hex"" + }); + + return signDelegation(users.bob, delegation_); + } +} + +interface IRolesAuthority { + function setPublicCapability(address target, bytes4 functionSig, bool enabled) external; +} From 89e74c46329511941c495aec147f3940cd60f491 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Fri, 6 Mar 2026 13:01:29 +0100 Subject: [PATCH 02/23] basic boring on chain queue implementation --- src/helpers/VedaAdapter.sol | 142 +++++++++++++++++- .../interfaces/IBoringOnChainQueue.sol | 31 ++++ test/helpers/VedaLending.t.sol | 60 +++++++- 3 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 src/helpers/interfaces/IBoringOnChainQueue.sol diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 23b4ed1f..f4c6a874 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -9,6 +9,7 @@ import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; import { Delegation, ModeCode } from "../utils/Types.sol"; import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; +import { IBoringOnChainQueue } from "./interfaces/IBoringOnChainQueue.sol"; /** * @title VedaAdapter @@ -68,6 +69,15 @@ contract VedaAdapter is Ownable2Step { uint256 minimumAssets; } + /** + * @notice Parameters for a single queued withdrawal operation in a batch + */ + struct QueueWithdrawParams { + Delegation[] delegations; + address token; + uint128 shareAmount; + } + ////////////////////////////// Events ////////////////////////////// /** @@ -94,6 +104,18 @@ contract VedaAdapter is Ownable2Step { address indexed delegator, address indexed delegate, address indexed token, uint256 shareAmount, uint256 assetsOut ); + /** + * @notice Emitted when a queued withdrawal is created via delegation + * @param delegator Address of the share owner (delegator) + * @param delegate Address of the executor (delegate) + * @param token Address of the underlying token requested + * @param shareAmount Amount of vault shares queued + * @param requestId The queue request identifier + */ + event QueueWithdrawExecuted( + address indexed delegator, address indexed delegate, address indexed token, uint128 shareAmount, bytes32 requestId + ); + /** * @notice Emitted when stuck tokens are withdrawn by owner * @param token Address of the token withdrawn @@ -119,6 +141,11 @@ contract VedaAdapter is Ownable2Step { /// @dev Thrown when msg.sender is not the leaf delegator error NotLeafDelegator(); + ////////////////////////////// Constants ////////////////////////////// + + uint16 public constant QUEUE_DISCOUNT = 0; + uint24 public constant QUEUE_SECONDS_TO_DEADLINE = 864000; // 10 days + ////////////////////////////// State ////////////////////////////// /** @@ -136,23 +163,38 @@ contract VedaAdapter is Ownable2Step { */ IVedaTeller public immutable teller; + /** + * @notice The BoringOnChainQueue contract for queued withdrawals + */ + IBoringOnChainQueue public immutable boringQueue; + ////////////////////////////// Constructor ////////////////////////////// /** - * @notice Initializes the adapter with delegation manager, BoringVault, and Teller addresses + * @notice Initializes the adapter with delegation manager, BoringVault, Teller, and Queue addresses * @param _owner Address of the contract owner * @param _delegationManager Address of the delegation manager contract * @param _boringVault Address of the BoringVault (token approval target) * @param _teller Address of the Teller contract (deposit entry point) + * @param _boringQueue Address of the BoringOnChainQueue contract (queued withdrawals) */ - constructor(address _owner, address _delegationManager, address _boringVault, address _teller) Ownable(_owner) { - if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0)) { + constructor( + address _owner, + address _delegationManager, + address _boringVault, + address _teller, + address _boringQueue + ) + Ownable(_owner) + { + if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0) || _boringQueue == address(0)) { revert InvalidZeroAddress(); } delegationManager = IDelegationManager(_delegationManager); boringVault = _boringVault; teller = IVedaTeller(_teller); + boringQueue = IBoringOnChainQueue(_boringQueue); } ////////////////////////////// External Methods ////////////////////////////// @@ -232,6 +274,45 @@ contract VedaAdapter is Ownable2Step { } } + /** + * @notice Queues a withdrawal from the BoringVault on-chain queue using delegation-based share transfer + * @dev Redeems the delegation to transfer vault shares to this adapter, approves the queue, + * then calls requestOnChainWithdraw. Funds are NOT automatically withdrawn; the request + * must be solved/settled separately after maturity. + * @param _delegations Array of Delegation objects, sorted leaf to root + * @param _token Address of the underlying token to receive upon settlement + * @param _shareAmount Amount of vault shares to queue for withdrawal + * @return requestId The queue request identifier + */ + function queueWithdrawByDelegation( + Delegation[] memory _delegations, + address _token, + uint128 _shareAmount + ) + external + returns (bytes32 requestId) + { + return _executeQueueWithdrawByDelegation(_delegations, _token, _shareAmount, msg.sender); + } + + /** + * @notice Queues multiple withdrawals using delegation streams, executed sequentially + * @param _queueWithdrawStreams Array of queue withdraw parameters + */ + function queueWithdrawByDelegationBatch(QueueWithdrawParams[] memory _queueWithdrawStreams) external { + uint256 streamsLength_ = _queueWithdrawStreams.length; + if (streamsLength_ == 0) revert InvalidBatchLength(); + + address caller_ = msg.sender; + for (uint256 i = 0; i < streamsLength_;) { + QueueWithdrawParams memory params_ = _queueWithdrawStreams[i]; + _executeQueueWithdrawByDelegation(params_.delegations, params_.token, params_.shareAmount, caller_); + unchecked { + ++i; + } + } + } + /** * @notice Emergency function to recover tokens accidentally sent to this contract * @dev This contract should never hold ERC20 tokens as all token operations are handled @@ -252,15 +333,16 @@ contract VedaAdapter is Ownable2Step { ////////////////////////////// Private/Internal Methods ////////////////////////////// /** - * @notice Ensures sufficient token allowance for BoringVault to pull tokens + * @notice Ensures sufficient token allowance for a spender to pull tokens * @dev Checks current allowance and sets exact amount if insufficient, avoiding accumulation * @param _token Token to manage allowance for + * @param _spender Address that needs to spend the tokens * @param _amount Amount needed for the operation */ - function _ensureAllowance(IERC20 _token, uint256 _amount) private { - uint256 allowance_ = _token.allowance(address(this), boringVault); + function _ensureAllowance(IERC20 _token, address _spender, uint256 _amount) private { + uint256 allowance_ = _token.allowance(address(this), _spender); if (allowance_ < _amount) { - _token.forceApprove(boringVault, _amount); + _token.forceApprove(_spender, _amount); } } @@ -302,7 +384,7 @@ contract VedaAdapter is Ownable2Step { delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); // Approve BoringVault to pull tokens, then deposit via Teller - _ensureAllowance(IERC20(_token), _amount); + _ensureAllowance(IERC20(_token), boringVault, _amount); uint256 shares_ = teller.bulkDeposit(_token, _amount, _minimumMint, rootDelegator_); emit DepositExecuted(rootDelegator_, _caller, _token, _amount, shares_); @@ -350,4 +432,48 @@ contract VedaAdapter is Ownable2Step { emit WithdrawExecuted(rootDelegator_, _caller, _token, _shareAmount, assetsOut_); } + + /** + * @notice Internal implementation of queued withdraw by delegation + * @param _delegations Delegation chain, sorted leaf to root + * @param _token Underlying token to receive upon settlement + * @param _shareAmount Amount of vault shares to queue + * @param _caller Authorized caller (must match leaf delegator) + * @return requestId The queue request identifier + */ + function _executeQueueWithdrawByDelegation( + Delegation[] memory _delegations, + address _token, + uint128 _shareAmount, + address _caller + ) + internal + returns (bytes32 requestId) + { + uint256 length_ = _delegations.length; + if (length_ < 2) revert InvalidDelegationsLength(); + if (_delegations[0].delegator != _caller) revert NotLeafDelegator(); + if (_token == address(0)) revert InvalidZeroAddress(); + + address rootDelegator_ = _delegations[length_ - 1].delegator; + + // Redeem delegation: transfer vault shares from user to this adapter + bytes[] memory permissionContexts_ = new bytes[](1); + permissionContexts_[0] = abi.encode(_delegations); + + ModeCode[] memory encodedModes_ = new ModeCode[](1); + encodedModes_[0] = ModeLib.encodeSimpleSingle(); + + bytes[] memory executionCallDatas_ = new bytes[](1); + bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), uint256(_shareAmount))); + executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, encodedTransfer_); + + delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); + + // Approve queue to pull vault shares, then create the queued withdrawal + _ensureAllowance(IERC20(boringVault), address(boringQueue), uint256(_shareAmount)); + requestId = boringQueue.requestOnChainWithdraw(_token, _shareAmount, QUEUE_DISCOUNT, QUEUE_SECONDS_TO_DEADLINE); + + emit QueueWithdrawExecuted(rootDelegator_, _caller, _token, _shareAmount, requestId); + } } diff --git a/src/helpers/interfaces/IBoringOnChainQueue.sol b/src/helpers/interfaces/IBoringOnChainQueue.sol new file mode 100644 index 00000000..6c7ac951 --- /dev/null +++ b/src/helpers/interfaces/IBoringOnChainQueue.sol @@ -0,0 +1,31 @@ +// Based on: +// https://github.com/Veda-Labs/boring-vault/blob/main/src/base/Roles/BoringQueue/BoringOnChainQueue.sol + +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +/** + * @title IBoringOnChainQueue + * @notice Interface for the BoringOnChainQueue's withdraw-request function. + * @dev Uses native Solidity types to avoid importing Veda-specific dependencies. + */ +interface IBoringOnChainQueue { + /** + * @notice Request an on-chain withdraw from the BoringVault queue. + * @dev The caller must have approved this queue contract to spend `amountOfShares` + * of the BoringVault share token. The queue pulls shares via `safeTransferFrom`. + * @param assetOut The underlying asset the user wants to receive upon maturity + * @param amountOfShares The amount of vault shares to queue for withdrawal + * @param discount The discount to apply in bps (0 = no discount) + * @param secondsToDeadline The time in seconds the request remains valid after maturity + * @return requestId A unique identifier for the queued withdraw request + */ + function requestOnChainWithdraw( + address assetOut, + uint128 amountOfShares, + uint16 discount, + uint24 secondsToDeadline + ) + external + returns (bytes32 requestId); +} diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index 95993cc0..f6a2f98a 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -54,6 +54,7 @@ contract VedaLendingTest is BaseTest { // Restricted vault - cannot set on behalfOf IVedaTeller public constant VEDA_TELLER = IVedaTeller(0xc46f2443b3521632E2E2a903D6da8f965B46f6a0); IERC20 public constant BORING_VAULT = IERC20(0xDbD87325D7b1189Dcc9255c4926076fF4a96A271); + address public constant BORING_QUEUE = 0x406E63323EF5d39D41C6fD895Ef9665AF926184c; address public constant ROLES_AUTHORITY = 0x1F53135155d6fF516bCcfDd9424fcdB8AD1eFB77; address public constant ROLES_AUTHORITY_OWNER = 0x846abf72fE789cf52FDefB0e924bE9E3670667DA; @@ -102,7 +103,7 @@ contract VedaLendingTest is BaseTest { redeemerEnforcer = new RedeemerEnforcer(); limitedCallsEnforcer = new LimitedCallsEnforcer(); logicalOrWrapperEnforcer = new LogicalOrWrapperEnforcer(delegationManager); - vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); vm.label(address(allowedTargetsEnforcer), "AllowedTargetsEnforcer"); vm.label(address(allowedMethodsEnforcer), "AllowedMethodsEnforcer"); @@ -111,6 +112,7 @@ contract VedaLendingTest is BaseTest { vm.label(address(logicalOrWrapperEnforcer), "LogicalOrWrapperEnforcer"); vm.label(address(erc20TransferAmountEnforcer), "ERC20TransferAmountEnforcer"); vm.label(address(vedaAdapter), "VedaAdapter"); + vm.label(BORING_QUEUE, "Veda BoringQueue"); vm.label(address(BORING_VAULT), "Veda BoringVault"); vm.label(address(VEDA_TELLER), "Veda Teller"); vm.label(address(USDC), "USDC"); @@ -247,34 +249,42 @@ contract VedaLendingTest is BaseTest { /// @notice Constructor must revert when delegationManager is zero address function test_constructor_revertsOnZeroDelegationManager() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER)); + new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); } /// @notice Constructor must revert when boringVault is zero address function test_constructor_revertsOnZeroBoringVault() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER)); + new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER), BORING_QUEUE); } /// @notice Constructor must revert when teller is zero address function test_constructor_revertsOnZeroTeller() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0)); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0), BORING_QUEUE); + } + + /// @notice Constructor must revert when boringQueue is zero address + function test_constructor_revertsOnZeroBoringQueue() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(0)); } /// @notice Constructor must revert when owner is zero address (OZ Ownable) function test_constructor_revertsOnZeroOwner() public { vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0))); - new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); } /// @notice Constructor must store immutable state correctly with valid inputs function test_constructor_successWithValidAddresses() public { - VedaAdapter newAdapter_ = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + VedaAdapter newAdapter_ = + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); assertEq(address(newAdapter_.delegationManager()), address(delegationManager)); assertEq(newAdapter_.boringVault(), address(BORING_VAULT)); assertEq(address(newAdapter_.teller()), address(VEDA_TELLER)); + assertEq(address(newAdapter_.boringQueue()), BORING_QUEUE); assertEq(newAdapter_.owner(), owner); } @@ -571,7 +581,40 @@ contract VedaLendingTest is BaseTest { } // ================================================================================== - // Section 8: Emergency Withdraw Tests + // Section 8: Queue Withdraw Tests + // Validates queueWithdrawByDelegation using the deployed BoringOnChainQueue. + // ================================================================================== + + /// @notice Queuing a withdrawal via delegation should transfer shares to the queue and return a valid requestId + function test_queueWithdraw_viaAdapterDelegation() public { + _depositViaAdapter(DEPOSIT_AMOUNT, 50); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + assertGt(aliceShares_, 0, "Alice should have vault shares after deposit"); + + uint128 shareAmount_ = uint128(aliceShares_); + uint256 queueSharesBefore_ = BORING_VAULT.balanceOf(BORING_QUEUE); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), uint256(shareAmount_)); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + bytes32 requestId_ = vedaAdapter.queueWithdrawByDelegation(delegations_, address(USDC), shareAmount_); + + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0, "Alice should have no shares after queue"); + assertEq(BORING_VAULT.balanceOf(BORING_QUEUE) - queueSharesBefore_, aliceShares_, "Queue should receive the shares"); + assertTrue(requestId_ != bytes32(0), "Request ID should be non-zero"); + } + + // ================================================================================== + // Section 9: Emergency Withdraw Tests // Validates the owner-only withdrawEmergency function for recovering stuck tokens. // ================================================================================== @@ -616,7 +659,7 @@ contract VedaLendingTest is BaseTest { } // ================================================================================== - // Section 9: Edge Cases and Security Validation + // Section 10: Edge Cases and Security Validation // Tests for subtle behaviors, allowance management, chain integrity, and token mismatch. // ================================================================================== @@ -887,3 +930,4 @@ contract VedaLendingTest is BaseTest { interface IRolesAuthority { function setPublicCapability(address target, bytes4 functionSig, bool enabled) external; } + From 7dc22459798c44e476244084162d19f375bb0403 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 30 Mar 2026 09:29:25 +0200 Subject: [PATCH 03/23] Move to arbitrum mainnet tests, implement new deposit function, remove withdraw via boring queue --- src/helpers/VedaAdapter.sol | 113 +----------------- .../interfaces/IBoringOnChainQueue.sol | 31 ----- src/helpers/interfaces/IVedaTeller.sol | 58 +-------- test/helpers/VedaLending.t.sol | 95 +++------------ 4 files changed, 27 insertions(+), 270 deletions(-) delete mode 100644 src/helpers/interfaces/IBoringOnChainQueue.sol diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index f4c6a874..5006c9dc 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -9,7 +9,6 @@ import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol"; import { Delegation, ModeCode } from "../utils/Types.sol"; import { IDelegationManager } from "../interfaces/IDelegationManager.sol"; import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; -import { IBoringOnChainQueue } from "./interfaces/IBoringOnChainQueue.sol"; /** * @title VedaAdapter @@ -36,12 +35,11 @@ import { IBoringOnChainQueue } from "./interfaces/IBoringOnChainQueue.sol"; * - Limited calls enforcer restricting the delegation to a single execution * * 3. For deposits: the adapter redeems the delegation chain, transfers tokens from the user to itself, - * approves the BoringVault, and calls `teller.bulkDeposit()` to mint shares to the user. + * approves the BoringVault, and calls `teller.deposit()` to mint shares to the user. * For withdrawals: the adapter redeems the delegation chain, transfers vault shares from the user * to itself, and calls `teller.withdraw()` to burn shares and send underlying assets to the user. * * Requirements: - * - VedaAdapter must be granted SOLVER_ROLE (or equivalent auth) on the Teller for deposits * - VedaAdapter must approve the BoringVault to spend deposit tokens */ contract VedaAdapter is Ownable2Step { @@ -141,11 +139,6 @@ contract VedaAdapter is Ownable2Step { /// @dev Thrown when msg.sender is not the leaf delegator error NotLeafDelegator(); - ////////////////////////////// Constants ////////////////////////////// - - uint16 public constant QUEUE_DISCOUNT = 0; - uint24 public constant QUEUE_SECONDS_TO_DEADLINE = 864000; // 10 days - ////////////////////////////// State ////////////////////////////// /** @@ -163,11 +156,6 @@ contract VedaAdapter is Ownable2Step { */ IVedaTeller public immutable teller; - /** - * @notice The BoringOnChainQueue contract for queued withdrawals - */ - IBoringOnChainQueue public immutable boringQueue; - ////////////////////////////// Constructor ////////////////////////////// /** @@ -176,25 +164,15 @@ contract VedaAdapter is Ownable2Step { * @param _delegationManager Address of the delegation manager contract * @param _boringVault Address of the BoringVault (token approval target) * @param _teller Address of the Teller contract (deposit entry point) - * @param _boringQueue Address of the BoringOnChainQueue contract (queued withdrawals) */ - constructor( - address _owner, - address _delegationManager, - address _boringVault, - address _teller, - address _boringQueue - ) - Ownable(_owner) - { - if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0) || _boringQueue == address(0)) { + constructor(address _owner, address _delegationManager, address _boringVault, address _teller) Ownable(_owner) { + if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0)) { revert InvalidZeroAddress(); } delegationManager = IDelegationManager(_delegationManager); boringVault = _boringVault; teller = IVedaTeller(_teller); - boringQueue = IBoringOnChainQueue(_boringQueue); } ////////////////////////////// External Methods ////////////////////////////// @@ -274,45 +252,6 @@ contract VedaAdapter is Ownable2Step { } } - /** - * @notice Queues a withdrawal from the BoringVault on-chain queue using delegation-based share transfer - * @dev Redeems the delegation to transfer vault shares to this adapter, approves the queue, - * then calls requestOnChainWithdraw. Funds are NOT automatically withdrawn; the request - * must be solved/settled separately after maturity. - * @param _delegations Array of Delegation objects, sorted leaf to root - * @param _token Address of the underlying token to receive upon settlement - * @param _shareAmount Amount of vault shares to queue for withdrawal - * @return requestId The queue request identifier - */ - function queueWithdrawByDelegation( - Delegation[] memory _delegations, - address _token, - uint128 _shareAmount - ) - external - returns (bytes32 requestId) - { - return _executeQueueWithdrawByDelegation(_delegations, _token, _shareAmount, msg.sender); - } - - /** - * @notice Queues multiple withdrawals using delegation streams, executed sequentially - * @param _queueWithdrawStreams Array of queue withdraw parameters - */ - function queueWithdrawByDelegationBatch(QueueWithdrawParams[] memory _queueWithdrawStreams) external { - uint256 streamsLength_ = _queueWithdrawStreams.length; - if (streamsLength_ == 0) revert InvalidBatchLength(); - - address caller_ = msg.sender; - for (uint256 i = 0; i < streamsLength_;) { - QueueWithdrawParams memory params_ = _queueWithdrawStreams[i]; - _executeQueueWithdrawByDelegation(params_.delegations, params_.token, params_.shareAmount, caller_); - unchecked { - ++i; - } - } - } - /** * @notice Emergency function to recover tokens accidentally sent to this contract * @dev This contract should never hold ERC20 tokens as all token operations are handled @@ -385,7 +324,7 @@ contract VedaAdapter is Ownable2Step { // Approve BoringVault to pull tokens, then deposit via Teller _ensureAllowance(IERC20(_token), boringVault, _amount); - uint256 shares_ = teller.bulkDeposit(_token, _amount, _minimumMint, rootDelegator_); + uint256 shares_ = teller.deposit(_token, _amount, _minimumMint, rootDelegator_, address(0)); emit DepositExecuted(rootDelegator_, _caller, _token, _amount, shares_); } @@ -432,48 +371,4 @@ contract VedaAdapter is Ownable2Step { emit WithdrawExecuted(rootDelegator_, _caller, _token, _shareAmount, assetsOut_); } - - /** - * @notice Internal implementation of queued withdraw by delegation - * @param _delegations Delegation chain, sorted leaf to root - * @param _token Underlying token to receive upon settlement - * @param _shareAmount Amount of vault shares to queue - * @param _caller Authorized caller (must match leaf delegator) - * @return requestId The queue request identifier - */ - function _executeQueueWithdrawByDelegation( - Delegation[] memory _delegations, - address _token, - uint128 _shareAmount, - address _caller - ) - internal - returns (bytes32 requestId) - { - uint256 length_ = _delegations.length; - if (length_ < 2) revert InvalidDelegationsLength(); - if (_delegations[0].delegator != _caller) revert NotLeafDelegator(); - if (_token == address(0)) revert InvalidZeroAddress(); - - address rootDelegator_ = _delegations[length_ - 1].delegator; - - // Redeem delegation: transfer vault shares from user to this adapter - bytes[] memory permissionContexts_ = new bytes[](1); - permissionContexts_[0] = abi.encode(_delegations); - - ModeCode[] memory encodedModes_ = new ModeCode[](1); - encodedModes_[0] = ModeLib.encodeSimpleSingle(); - - bytes[] memory executionCallDatas_ = new bytes[](1); - bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), uint256(_shareAmount))); - executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, encodedTransfer_); - - delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); - - // Approve queue to pull vault shares, then create the queued withdrawal - _ensureAllowance(IERC20(boringVault), address(boringQueue), uint256(_shareAmount)); - requestId = boringQueue.requestOnChainWithdraw(_token, _shareAmount, QUEUE_DISCOUNT, QUEUE_SECONDS_TO_DEADLINE); - - emit QueueWithdrawExecuted(rootDelegator_, _caller, _token, _shareAmount, requestId); - } } diff --git a/src/helpers/interfaces/IBoringOnChainQueue.sol b/src/helpers/interfaces/IBoringOnChainQueue.sol deleted file mode 100644 index 6c7ac951..00000000 --- a/src/helpers/interfaces/IBoringOnChainQueue.sol +++ /dev/null @@ -1,31 +0,0 @@ -// Based on: -// https://github.com/Veda-Labs/boring-vault/blob/main/src/base/Roles/BoringQueue/BoringOnChainQueue.sol - -// SPDX-License-Identifier: MIT AND Apache-2.0 -pragma solidity 0.8.23; - -/** - * @title IBoringOnChainQueue - * @notice Interface for the BoringOnChainQueue's withdraw-request function. - * @dev Uses native Solidity types to avoid importing Veda-specific dependencies. - */ -interface IBoringOnChainQueue { - /** - * @notice Request an on-chain withdraw from the BoringVault queue. - * @dev The caller must have approved this queue contract to spend `amountOfShares` - * of the BoringVault share token. The queue pulls shares via `safeTransferFrom`. - * @param assetOut The underlying asset the user wants to receive upon maturity - * @param amountOfShares The amount of vault shares to queue for withdrawal - * @param discount The discount to apply in bps (0 = no discount) - * @param secondsToDeadline The time in seconds the request remains valid after maturity - * @return requestId A unique identifier for the queued withdraw request - */ - function requestOnChainWithdraw( - address assetOut, - uint128 amountOfShares, - uint16 discount, - uint24 secondsToDeadline - ) - external - returns (bytes32 requestId); -} diff --git a/src/helpers/interfaces/IVedaTeller.sol b/src/helpers/interfaces/IVedaTeller.sol index c40daaf5..986e84c6 100644 --- a/src/helpers/interfaces/IVedaTeller.sol +++ b/src/helpers/interfaces/IVedaTeller.sol @@ -33,46 +33,18 @@ interface IVedaTeller { returns (uint256 shares); /** - * @notice Allows users to deposit into the BoringVault using ERC-2612 permit. - * @dev Shares are minted to `msg.sender`. A share lock period may apply. - * @param depositAsset The ERC20 token to deposit - * @param depositAmount The amount to deposit - * @param minimumMint The minimum shares the user expects to receive - * @param deadline The permit deadline timestamp - * @param v The permit signature v value - * @param r The permit signature r value - * @param s The permit signature s value - * @return shares The number of vault shares minted - */ - function depositWithPermit( - address depositAsset, - uint256 depositAmount, - uint256 minimumMint, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) - external - returns (uint256 shares); - - /** - * @notice Allows SOLVER_ROLE to deposit on behalf of a recipient. - * @dev Tokens are pulled from `msg.sender`; shares are minted to `to`. - * No share lock period applies to bulk deposits. - * @param depositAsset The ERC20 token to deposit - * @param depositAmount The amount to deposit - * @param minimumMint The minimum shares expected - * @param to The address that will receive the vault shares - * @return shares The number of vault shares minted + * @notice Allows an authorized caller to deposit into the BoringVault for another address, if this contract is not paused. + * @dev Intended for router-like integrations; this selector should remain role-gated. */ - function bulkDeposit( + function deposit( address depositAsset, uint256 depositAmount, uint256 minimumMint, - address to + address to, + address referralAddress ) external + payable returns (uint256 shares); /** @@ -93,22 +65,4 @@ interface IVedaTeller { ) external returns (uint256 assetsOut); - - /** - * @notice Allows SOLVER_ROLE to withdraw on behalf of a recipient. - * @dev Shares are burned from `msg.sender`; underlying assets are sent to `to`. - * @param withdrawAsset The ERC20 token to receive - * @param shareAmount The amount of vault shares to burn - * @param minimumAssets The minimum underlying assets expected - * @param to The address that will receive the underlying assets - * @return assetsOut The amount of underlying assets sent - */ - function bulkWithdraw( - address withdrawAsset, - uint256 shareAmount, - uint256 minimumAssets, - address to - ) - external - returns (uint256 assetsOut); } diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index f6a2f98a..13ab96c4 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -31,7 +31,7 @@ import { BasicERC20 } from "../utils/BasicERC20.t.sol"; /** * @title VedaLending Test * @notice Tests delegation-based lending on Veda BoringVault. - * @dev Uses a forked Ink mainnet environment to test real contract interactions. + * @dev Uses a forked Arbitrum mainnet environment to test real contract interactions. * * Veda BoringVault implements the ERC-4626 standard for tokenized vaults: * - Users deposit assets (e.g., USDC) and receive vault shares representing proportional ownership @@ -40,8 +40,6 @@ import { BasicERC20 } from "../utils/BasicERC20.t.sol"; * - Veda uses multiple contracts to manage the flow of funds: * - We implement Teller for deposits and withdrawals * - We implement BoringVault for the approval and custody of assets - * - We modify the mainnet deployment to allow public access to the Teller functions for testing (in production, we would get Solver - * role on Adapter) * - More docs here: https://docs.veda.tech/architecture-and-flow-of-funds * * - Security considerations: @@ -52,15 +50,11 @@ contract VedaLendingTest is BaseTest { using ModeLib for ModeCode; // Restricted vault - cannot set on behalfOf - IVedaTeller public constant VEDA_TELLER = IVedaTeller(0xc46f2443b3521632E2E2a903D6da8f965B46f6a0); - IERC20 public constant BORING_VAULT = IERC20(0xDbD87325D7b1189Dcc9255c4926076fF4a96A271); - address public constant BORING_QUEUE = 0x406E63323EF5d39D41C6fD895Ef9665AF926184c; + IVedaTeller public constant VEDA_TELLER = IVedaTeller(0x86821F179eaD9F0b3C79b2f8deF0227eEBFDc9f9); + IERC20 public constant BORING_VAULT = IERC20(0xB5F07d769dD60fE54c97dd53101181073DDf21b2); - address public constant ROLES_AUTHORITY = 0x1F53135155d6fF516bCcfDd9424fcdB8AD1eFB77; - address public constant ROLES_AUTHORITY_OWNER = 0x846abf72fE789cf52FDefB0e924bE9E3670667DA; - - IERC20 public constant USDC = IERC20(0x2D270e6886d130D724215A266106e6832161EAEd); - address public constant USDC_WHALE = 0xd3abC2b515345E47D41C0A1Cd64F8493B80d1ad6; + IERC20 public constant USDC = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831); + address public constant USDC_WHALE = 0xC6962004f452bE9203591991D15f6b388e09E8D0; address public owner; // Enforcers for delegation restrictions @@ -74,7 +68,7 @@ contract VedaLendingTest is BaseTest { LimitedCallsEnforcer public limitedCallsEnforcer; VedaAdapter public vedaAdapter; - uint256 public constant MAINNET_FORK_BLOCK = 38688994; // Use latest available block + uint256 public constant MAINNET_FORK_BLOCK = 447148700; // Use latest available block uint256 public constant INITIAL_USD_BALANCE = 10000000000; // 10k USDC uint256 public constant DEPOSIT_AMOUNT = 1000000000; // 1k USDC uint256 public constant SHARE_LOCK_SECONDS = 61; // Warp past the 60s share lock period applied by deposit() @@ -83,7 +77,7 @@ contract VedaLendingTest is BaseTest { function setUp() public override { // Create fork from mainnet at specific block - vm.createSelectFork(vm.envString("INK_RPC_URL"), MAINNET_FORK_BLOCK); + vm.createSelectFork(vm.envString("ARBITRUM_RPC_URL"), MAINNET_FORK_BLOCK); // Set implementation type IMPLEMENTATION = Implementation.Hybrid; @@ -103,7 +97,7 @@ contract VedaLendingTest is BaseTest { redeemerEnforcer = new RedeemerEnforcer(); limitedCallsEnforcer = new LimitedCallsEnforcer(); logicalOrWrapperEnforcer = new LogicalOrWrapperEnforcer(delegationManager); - vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); + vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); vm.label(address(allowedTargetsEnforcer), "AllowedTargetsEnforcer"); vm.label(address(allowedMethodsEnforcer), "AllowedMethodsEnforcer"); @@ -112,7 +106,6 @@ contract VedaLendingTest is BaseTest { vm.label(address(logicalOrWrapperEnforcer), "LogicalOrWrapperEnforcer"); vm.label(address(erc20TransferAmountEnforcer), "ERC20TransferAmountEnforcer"); vm.label(address(vedaAdapter), "VedaAdapter"); - vm.label(BORING_QUEUE, "Veda BoringQueue"); vm.label(address(BORING_VAULT), "Veda BoringVault"); vm.label(address(VEDA_TELLER), "Veda Teller"); vm.label(address(USDC), "USDC"); @@ -123,9 +116,6 @@ contract VedaLendingTest is BaseTest { vm.prank(USDC_WHALE); USDC.transfer(address(users.alice.deleGator), INITIAL_USD_BALANCE); // 10k USDC - - // Make solver-gated Teller functions publicly callable on the fork - _enableVedaTellerAccess(); } // ================================================================================== @@ -249,42 +239,34 @@ contract VedaLendingTest is BaseTest { /// @notice Constructor must revert when delegationManager is zero address function test_constructor_revertsOnZeroDelegationManager() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); + new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER)); } /// @notice Constructor must revert when boringVault is zero address function test_constructor_revertsOnZeroBoringVault() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER), BORING_QUEUE); + new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER)); } /// @notice Constructor must revert when teller is zero address function test_constructor_revertsOnZeroTeller() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0), BORING_QUEUE); - } - - /// @notice Constructor must revert when boringQueue is zero address - function test_constructor_revertsOnZeroBoringQueue() public { - vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(0)); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0)); } /// @notice Constructor must revert when owner is zero address (OZ Ownable) function test_constructor_revertsOnZeroOwner() public { vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0))); - new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); + new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); } /// @notice Constructor must store immutable state correctly with valid inputs function test_constructor_successWithValidAddresses() public { - VedaAdapter newAdapter_ = - new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), BORING_QUEUE); + VedaAdapter newAdapter_ = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); assertEq(address(newAdapter_.delegationManager()), address(delegationManager)); assertEq(newAdapter_.boringVault(), address(BORING_VAULT)); assertEq(address(newAdapter_.teller()), address(VEDA_TELLER)); - assertEq(address(newAdapter_.boringQueue()), BORING_QUEUE); assertEq(newAdapter_.owner(), owner); } @@ -555,8 +537,9 @@ contract VedaLendingTest is BaseTest { /// @notice Batch withdraw with 2 independent delegation chains in a single transaction function test_withdrawByDelegationBatch_twoDelegationChains() public { - // Setup: Deposit via adapter to create shares (bulkDeposit skips share lock) + // Setup: Deposit via adapter to create shares, then warp past the 60s share lock period _depositViaAdapter(DEPOSIT_AMOUNT, 10); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); uint256 totalShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); assertGt(totalShares_, 0, "Alice should have shares after deposit"); @@ -581,40 +564,7 @@ contract VedaLendingTest is BaseTest { } // ================================================================================== - // Section 8: Queue Withdraw Tests - // Validates queueWithdrawByDelegation using the deployed BoringOnChainQueue. - // ================================================================================== - - /// @notice Queuing a withdrawal via delegation should transfer shares to the queue and return a valid requestId - function test_queueWithdraw_viaAdapterDelegation() public { - _depositViaAdapter(DEPOSIT_AMOUNT, 50); - - uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); - assertGt(aliceShares_, 0, "Alice should have vault shares after deposit"); - - uint128 shareAmount_ = uint128(aliceShares_); - uint256 queueSharesBefore_ = BORING_VAULT.balanceOf(BORING_QUEUE); - - Delegation memory delegation_ = _createTransferDelegation( - address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max - ); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), uint256(shareAmount_)); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - vm.prank(address(users.bob.deleGator)); - bytes32 requestId_ = vedaAdapter.queueWithdrawByDelegation(delegations_, address(USDC), shareAmount_); - - assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0, "Alice should have no shares after queue"); - assertEq(BORING_VAULT.balanceOf(BORING_QUEUE) - queueSharesBefore_, aliceShares_, "Queue should receive the shares"); - assertTrue(requestId_ != bytes32(0), "Request ID should be non-zero"); - } - - // ================================================================================== - // Section 9: Emergency Withdraw Tests + // Section 8: Emergency Withdraw Tests // Validates the owner-only withdrawEmergency function for recovering stuck tokens. // ================================================================================== @@ -659,7 +609,7 @@ contract VedaLendingTest is BaseTest { } // ================================================================================== - // Section 10: Edge Cases and Security Validation + // Section 9: Edge Cases and Security Validation // Tests for subtle behaviors, allowance management, chain integrity, and token mismatch. // ================================================================================== @@ -792,17 +742,6 @@ contract VedaLendingTest is BaseTest { // Helper Functions // ================================================================================== - /// @notice Pranks into the RolesAuthority owner to make solver-gated Teller functions publicly callable - function _enableVedaTellerAccess() internal { - IRolesAuthority rolesAuthority_ = IRolesAuthority(ROLES_AUTHORITY); - - vm.prank(ROLES_AUTHORITY_OWNER); - rolesAuthority_.setPublicCapability(address(VEDA_TELLER), IVedaTeller.bulkDeposit.selector, true); - - vm.prank(ROLES_AUTHORITY_OWNER); - rolesAuthority_.setPublicCapability(address(VEDA_TELLER), IVedaTeller.bulkWithdraw.selector, true); - } - /// @notice Sets up initial lending state (Alice deposits USDC to get vault shares) function _setupLendingState() internal { vm.prank(address(users.alice.deleGator)); From b0a56ede1e8a2c6d0867e2a6caebca46bd4c76da Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 30 Mar 2026 09:50:27 +0200 Subject: [PATCH 04/23] Clean up docs, add batch events --- src/helpers/VedaAdapter.sol | 41 ++++++++++++++++------------------ test/helpers/VedaLending.t.sol | 17 +++++++------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 5006c9dc..1cdf226d 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -20,8 +20,8 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * Architecture: * - BoringVault: The ERC20 vault share token that also custodies assets. On deposit, the vault pulls * tokens from the caller via `safeTransferFrom`, so this adapter must approve the BoringVault. - * - Teller: The contract that orchestrates deposits/withdrawals. The adapter calls `teller.bulkDeposit()` - * for deposits (requires SOLVER_ROLE) and `teller.withdraw()` for withdrawals (user-facing, no special + * - Teller: The contract that orchestrates deposits/withdrawals. The adapter calls `teller.deposit()` + * for deposits and `teller.withdraw()` for withdrawals (user-facing, no special * role needed). * * Delegation Flow: @@ -67,15 +67,6 @@ contract VedaAdapter is Ownable2Step { uint256 minimumAssets; } - /** - * @notice Parameters for a single queued withdrawal operation in a batch - */ - struct QueueWithdrawParams { - Delegation[] delegations; - address token; - uint128 shareAmount; - } - ////////////////////////////// Events ////////////////////////////// /** @@ -103,16 +94,18 @@ contract VedaAdapter is Ownable2Step { ); /** - * @notice Emitted when a queued withdrawal is created via delegation - * @param delegator Address of the share owner (delegator) - * @param delegate Address of the executor (delegate) - * @param token Address of the underlying token requested - * @param shareAmount Amount of vault shares queued - * @param requestId The queue request identifier + * @notice Emitted when a batch deposit is completed + * @param caller Address of the batch executor + * @param count Number of deposit streams executed */ - event QueueWithdrawExecuted( - address indexed delegator, address indexed delegate, address indexed token, uint128 shareAmount, bytes32 requestId - ); + event BatchDepositExecuted(address indexed caller, uint256 count); + + /** + * @notice Emitted when a batch withdrawal is completed + * @param caller Address of the batch executor + * @param count Number of withdrawal streams executed + */ + event BatchWithdrawExecuted(address indexed caller, uint256 count); /** * @notice Emitted when stuck tokens are withdrawn by owner @@ -159,7 +152,7 @@ contract VedaAdapter is Ownable2Step { ////////////////////////////// Constructor ////////////////////////////// /** - * @notice Initializes the adapter with delegation manager, BoringVault, Teller, and Queue addresses + * @notice Initializes the adapter with delegation manager, BoringVault, and Teller addresses * @param _owner Address of the contract owner * @param _delegationManager Address of the delegation manager contract * @param _boringVault Address of the BoringVault (token approval target) @@ -179,7 +172,7 @@ contract VedaAdapter is Ownable2Step { /** * @notice Deposits tokens into a Veda BoringVault using delegation-based token transfer - * @dev Redeems the delegation to transfer tokens to this adapter, then calls bulkDeposit + * @dev Redeems the delegation to transfer tokens to this adapter, then calls deposit * on the Teller which mints vault shares directly to the original token owner. * Requires at least 2 delegations forming a chain from user to operator to this adapter. * @param _delegations Array of Delegation objects, sorted leaf to root @@ -209,6 +202,8 @@ contract VedaAdapter is Ownable2Step { ++i; } } + + emit BatchDepositExecuted(caller_, streamsLength_); } /** @@ -250,6 +245,8 @@ contract VedaAdapter is Ownable2Step { ++i; } } + + emit BatchWithdrawExecuted(caller_, streamsLength_); } /** diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index 13ab96c4..fb256d1c 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -153,8 +153,6 @@ contract VedaLendingTest is BaseTest { // Withdraw all shares back to USDC vm.prank(address(users.alice.deleGator)); - BORING_VAULT.approve(address(BORING_VAULT), aliceShares_); - vm.prank(address(users.alice.deleGator)); uint256 assetsOut_ = VEDA_TELLER.withdraw(address(USDC), aliceShares_, 0, address(users.alice.deleGator)); assertGt(assetsOut_, 0, "Should receive assets back"); @@ -525,6 +523,9 @@ contract VedaLendingTest is BaseTest { streams_[1] = VedaAdapter.DepositParams({ delegations: delegations2_, token: address(USDC), amount: amount2_, minimumMint: 0 }); + vm.expectEmit(true, true, true, true, address(vedaAdapter)); + emit VedaAdapter.BatchDepositExecuted(address(users.bob.deleGator), 2); + vm.prank(address(users.bob.deleGator)); vedaAdapter.depositByDelegationBatch(streams_); @@ -551,6 +552,9 @@ contract VedaLendingTest is BaseTest { wdStreams_[0] = _buildWithdrawParams(sharesPart1_, 20); wdStreams_[1] = _buildWithdrawParams(sharesPart2_, 21); + vm.expectEmit(true, true, true, true, address(vedaAdapter)); + emit VedaAdapter.BatchWithdrawExecuted(address(users.bob.deleGator), 2); + vm.prank(address(users.bob.deleGator)); vedaAdapter.withdrawByDelegationBatch(wdStreams_); @@ -653,7 +657,7 @@ contract VedaLendingTest is BaseTest { assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); } - /// @notice BoringVault must fully consume the allowance granted by the adapter during bulkDeposit. + /// @notice BoringVault must fully consume the allowance granted by the adapter during deposit. /// Verifies that _ensureAllowance does not cause unbounded allowance accumulation. function test_allowanceFullyConsumedAfterDeposit() public { assertEq(USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), 0, "Initial allowance should be 0"); @@ -673,7 +677,7 @@ contract VedaLendingTest is BaseTest { assertEq( USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), 0, - "Allowance must be fully consumed after bulkDeposit -- no residual accumulation" + "Allowance must be fully consumed after deposit -- no residual accumulation" ); } @@ -865,8 +869,3 @@ contract VedaLendingTest is BaseTest { return signDelegation(users.bob, delegation_); } } - -interface IRolesAuthority { - function setPublicCapability(address target, bytes4 functionSig, bool enabled) external; -} - From 5af6309bcbc972501fbcac781e78460b7c75e17c Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 30 Mar 2026 09:54:34 +0200 Subject: [PATCH 05/23] add arbitrum rpc to tests --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ba85d68..68d998ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,3 +51,4 @@ jobs: run: forge test -vvv env: LINEA_RPC_URL: ${{ secrets.LINEA_RPC_URL }} + ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }} From 0f8ee51147883392fbcad5ed17be1d462ffb7282 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 30 Mar 2026 10:04:31 +0200 Subject: [PATCH 06/23] set API key in tests --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 68d998ad..09fc09bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,3 +52,4 @@ jobs: env: LINEA_RPC_URL: ${{ secrets.LINEA_RPC_URL }} ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }} + RPC_API_KEY: ${{ secrets.RPC_API_KEY }} From 25a8c195fde3e3a20eba4367ead6c98ddf4f02c2 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 30 Mar 2026 13:11:27 +0200 Subject: [PATCH 07/23] add veda deployment script --- script/DeployVedaAdapter.s.sol | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 script/DeployVedaAdapter.s.sol diff --git a/script/DeployVedaAdapter.s.sol b/script/DeployVedaAdapter.s.sol new file mode 100644 index 00000000..cf63862f --- /dev/null +++ b/script/DeployVedaAdapter.s.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT AND Apache-2.0 +pragma solidity 0.8.23; + +import "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; + +import { VedaAdapter } from "../src/helpers/VedaAdapter.sol"; + +/** + * @title DeployVedaAdapter + * @notice Deploys the VedaAdapter contract. + * @dev Update the hardcoded addresses below before deploying. + * @dev Fill the SALT variable in the .env file + * @dev run the script with: + * forge script script/DeployVedaAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast + */ +contract DeployVedaAdapter is Script { + // Hardcoded constructor parameters - update these before deploying + address constant OWNER = address(0x0000000000000000000000000000000000000000); + address constant DELEGATION_MANAGER = address(0x0000000000000000000000000000000000000000); + address constant BORING_VAULT = address(0x0000000000000000000000000000000000000000); + address constant VEDA_TELLER = address(0x0000000000000000000000000000000000000000); + + bytes32 salt; + address deployer; + + function setUp() public { + salt = bytes32(abi.encodePacked(vm.envString("SALT"))); + deployer = msg.sender; + console2.log("~~~"); + console2.log("Owner: %s", OWNER); + console2.log("DelegationManager: %s", DELEGATION_MANAGER); + console2.log("BoringVault: %s", BORING_VAULT); + console2.log("VedaTeller: %s", VEDA_TELLER); + console2.log("Deployer: %s", deployer); + console2.log("Salt:"); + console2.logBytes32(salt); + } + + function run() public { + console2.log("~~~"); + vm.startBroadcast(); + + address vedaAdapter = address(new VedaAdapter{ salt: salt }(OWNER, DELEGATION_MANAGER, BORING_VAULT, VEDA_TELLER)); + console2.log("VedaAdapter: %s", vedaAdapter); + + vm.stopBroadcast(); + } +} From 7b3fb1d7abdf4d35161a425424b39f68d9cf042c Mon Sep 17 00:00:00 2001 From: MoMannn Date: Tue, 31 Mar 2026 22:10:39 +0200 Subject: [PATCH 08/23] Remove NotLeafDelegator check and add security consideration --- src/helpers/VedaAdapter.sol | 31 ++++++++++++++++++---------- test/helpers/VedaLending.t.sol | 37 ---------------------------------- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 1cdf226d..765957bc 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -41,6 +41,14 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * * Requirements: * - VedaAdapter must approve the BoringVault to spend deposit tokens + * + * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no + * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the + * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended + * deposit or withdrawal amount. Once that amount is transferred the enforcer's running total is exhausted and + * any replay attempt will revert, making the delegation effectively single-use. A delegation without this + * enforcer (or with an amount larger than intended) could be exploited by any caller to transfer more tokens + * than authorised. */ contract VedaAdapter is Ownable2Step { using SafeERC20 for IERC20; @@ -129,9 +137,6 @@ contract VedaAdapter is Ownable2Step { /// @dev Thrown when the batch array is empty error InvalidBatchLength(); - /// @dev Thrown when msg.sender is not the leaf delegator - error NotLeafDelegator(); - ////////////////////////////// State ////////////////////////////// /** @@ -179,6 +184,8 @@ contract VedaAdapter is Ownable2Step { * @param _token Address of the token to deposit * @param _amount Amount of tokens to deposit * @param _minimumMint Minimum vault shares the user expects to receive (slippage protection) + * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an + * `ERC20TransferAmountEnforcer` capped to exactly `_amount` to prevent over-spending or replay. */ function depositByDelegation(Delegation[] memory _delegations, address _token, uint256 _amount, uint256 _minimumMint) external { _executeDepositByDelegation(_delegations, _token, _amount, _minimumMint, msg.sender); @@ -186,9 +193,10 @@ contract VedaAdapter is Ownable2Step { /** * @notice Deposits tokens using multiple delegation streams, executed sequentially - * @dev Each element is executed one after the other. The caller must be the delegator - * (first delegate in the chain) for each stream. + * @dev Each element is executed one after the other. * @param _depositStreams Array of deposit parameters + * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an + * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent over-spending or replay. */ function depositByDelegationBatch(DepositParams[] memory _depositStreams) external { uint256 streamsLength_ = _depositStreams.length; @@ -215,6 +223,8 @@ contract VedaAdapter is Ownable2Step { * @param _token Address of the underlying token to receive * @param _shareAmount Amount of vault shares to redeem * @param _minimumAssets Minimum underlying assets the user expects to receive (slippage protection) + * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an + * `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay. */ function withdrawByDelegation( Delegation[] memory _delegations, @@ -229,9 +239,10 @@ contract VedaAdapter is Ownable2Step { /** * @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially - * @dev Each element is executed one after the other. The caller must be the delegator - * (first delegate in the chain) for each stream. + * @dev Each element is executed one after the other. * @param _withdrawStreams Array of withdraw parameters + * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an + * `ERC20TransferAmountEnforcer` capped to exactly the intended share amount to prevent over-spending or replay. */ function withdrawByDelegationBatch(WithdrawParams[] memory _withdrawStreams) external { uint256 streamsLength_ = _withdrawStreams.length; @@ -288,7 +299,7 @@ contract VedaAdapter is Ownable2Step { * @param _token Token to deposit * @param _amount Amount to deposit * @param _minimumMint Minimum vault shares expected - * @param _caller Authorized caller (must match leaf delegator) + * @param _caller Address of the caller, used only for event emission */ function _executeDepositByDelegation( Delegation[] memory _delegations, @@ -301,7 +312,6 @@ contract VedaAdapter is Ownable2Step { { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); - if (_delegations[0].delegator != _caller) revert NotLeafDelegator(); if (_token == address(0)) revert InvalidZeroAddress(); address rootDelegator_ = _delegations[length_ - 1].delegator; @@ -332,7 +342,7 @@ contract VedaAdapter is Ownable2Step { * @param _token Underlying token to receive * @param _shareAmount Amount of vault shares to redeem * @param _minimumAssets Minimum underlying assets expected - * @param _caller Authorized caller (must match leaf delegator) + * @param _caller Address of the caller, used only for event emission */ function _executeWithdrawByDelegation( Delegation[] memory _delegations, @@ -345,7 +355,6 @@ contract VedaAdapter is Ownable2Step { { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); - if (_delegations[0].delegator != _caller) revert NotLeafDelegator(); if (_token == address(0)) revert InvalidZeroAddress(); address rootDelegator_ = _delegations[length_ - 1].delegator; diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index fb256d1c..59d3aea9 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -294,23 +294,6 @@ contract VedaLendingTest is BaseTest { vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); } - /// @notice depositByDelegation must revert when msg.sender does not match delegations[0].delegator - function test_depositByDelegation_revertsOnUnauthorizedCaller() public { - Delegation memory delegation_ = - _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - // Alice tries to call but Bob is delegations[0].delegator - vm.expectRevert(VedaAdapter.NotLeafDelegator.selector); - vm.prank(address(users.alice.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); - } - /// @notice depositByDelegation must revert when token address is zero function test_depositByDelegation_revertsOnZeroTokenAddress() public { Delegation memory delegation_ = @@ -377,26 +360,6 @@ contract VedaLendingTest is BaseTest { vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); } - /// @notice withdrawByDelegation must revert when msg.sender does not match delegations[0].delegator - function test_withdrawByDelegation_revertsOnUnauthorizedCaller() public { - _setupLendingState(); - - Delegation memory delegation_ = _createTransferDelegation( - address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max - ); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - // Alice tries to call but Bob is delegations[0].delegator - vm.expectRevert(VedaAdapter.NotLeafDelegator.selector); - vm.prank(address(users.alice.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); - } - /// @notice withdrawByDelegation must revert when token address is zero function test_withdrawByDelegation_revertsOnZeroTokenAddress() public { _setupLendingState(); From c02240065ccfd98fbca48ea79d504ac0f49a4704 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 2 Apr 2026 09:22:48 +0200 Subject: [PATCH 09/23] optimize inputs and add docs regarding senity check inputs --- src/helpers/VedaAdapter.sol | 111 ++++++++++++++++++++++----------- test/helpers/VedaLending.t.sol | 96 ++++++---------------------- 2 files changed, 93 insertions(+), 114 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 765957bc..d3a423bd 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -42,6 +42,12 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * Requirements: * - VedaAdapter must approve the BoringVault to spend deposit tokens * + * Leaf Caveat Format: + * - The first caveat of the leaf delegation (`_delegations[0].caveats[0]`) must follow the + * ERC20TransferAmountEnforcer terms format: abi.encodePacked(address token, uint256 amount) (52 bytes). + * The adapter parses token and amount directly from these terms instead of accepting them as + * function inputs, ensuring the caller cannot supply values that differ from what the delegator authorised. + * * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended @@ -60,8 +66,6 @@ contract VedaAdapter is Ownable2Step { */ struct DepositParams { Delegation[] delegations; - address token; - uint256 amount; uint256 minimumMint; } @@ -71,7 +75,6 @@ contract VedaAdapter is Ownable2Step { struct WithdrawParams { Delegation[] delegations; address token; - uint256 shareAmount; uint256 minimumAssets; } @@ -180,20 +183,26 @@ contract VedaAdapter is Ownable2Step { * @dev Redeems the delegation to transfer tokens to this adapter, then calls deposit * on the Teller which mints vault shares directly to the original token owner. * Requires at least 2 delegations forming a chain from user to operator to this adapter. + * The deposit token and amount are parsed from the first caveat of the leaf delegation + * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer + * format: abi.encodePacked(address token, uint256 amount). * @param _delegations Array of Delegation objects, sorted leaf to root - * @param _token Address of the token to deposit - * @param _amount Amount of tokens to deposit - * @param _minimumMint Minimum vault shares the user expects to receive (slippage protection) + * @param _minimumMint Minimum vault shares the caller expects to receive, used as a sanity-check + * bound. The Veda vault conversion is always at fair value; rate drift from yield streaming + * is negligible. A tolerance of 0.1-0.5% is recommended. If this check causes a revert, + * no funds are lost — retry with a fresh quote. * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an - * `ERC20TransferAmountEnforcer` capped to exactly `_amount` to prevent over-spending or replay. + * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent + * over-spending or replay. */ - function depositByDelegation(Delegation[] memory _delegations, address _token, uint256 _amount, uint256 _minimumMint) external { - _executeDepositByDelegation(_delegations, _token, _amount, _minimumMint, msg.sender); + function depositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint) external { + _executeDepositByDelegation(_delegations, _minimumMint, msg.sender); } /** * @notice Deposits tokens using multiple delegation streams, executed sequentially - * @dev Each element is executed one after the other. + * @dev Each element is executed one after the other. Token and amount for each stream are parsed + * from the first caveat of each stream's leaf delegation. * @param _depositStreams Array of deposit parameters * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent over-spending or replay. @@ -205,7 +214,7 @@ contract VedaAdapter is Ownable2Step { address caller_ = msg.sender; for (uint256 i = 0; i < streamsLength_;) { DepositParams memory params_ = _depositStreams[i]; - _executeDepositByDelegation(params_.delegations, params_.token, params_.amount, params_.minimumMint, caller_); + _executeDepositByDelegation(params_.delegations, params_.minimumMint, caller_); unchecked { ++i; } @@ -219,27 +228,32 @@ contract VedaAdapter is Ownable2Step { * @dev Redeems the delegation to transfer vault shares to this adapter, then calls withdraw * on the Teller which burns shares and sends underlying assets directly to the original share owner. * Requires at least 2 delegations forming a chain from user to operator to this adapter. + * The share amount is parsed from the first caveat of the leaf delegation + * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer + * format: abi.encodePacked(address boringVault, uint256 shareAmount). * @param _delegations Array of Delegation objects, sorted leaf to root - * @param _token Address of the underlying token to receive - * @param _shareAmount Amount of vault shares to redeem - * @param _minimumAssets Minimum underlying assets the user expects to receive (slippage protection) + * @param _token Address of the underlying token to receive from the vault + * @param _minimumAssets Minimum underlying assets the caller expects to receive, used as a + * sanity-check bound. The Veda vault conversion is always at fair value; rate drift from + * yield streaming is negligible. A tolerance of 0.1-0.5% is recommended. If this check + * causes a revert, no funds are lost — retry with a fresh quote. * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an * `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay. */ function withdrawByDelegation( Delegation[] memory _delegations, address _token, - uint256 _shareAmount, uint256 _minimumAssets ) external { - _executeWithdrawByDelegation(_delegations, _token, _shareAmount, _minimumAssets, msg.sender); + _executeWithdrawByDelegation(_delegations, _token, _minimumAssets, msg.sender); } /** * @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially - * @dev Each element is executed one after the other. + * @dev Each element is executed one after the other. The share amount for each stream is parsed + * from the first caveat of each stream's leaf delegation. * @param _withdrawStreams Array of withdraw parameters * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an * `ERC20TransferAmountEnforcer` capped to exactly the intended share amount to prevent over-spending or replay. @@ -251,7 +265,7 @@ contract VedaAdapter is Ownable2Step { address caller_ = msg.sender; for (uint256 i = 0; i < streamsLength_;) { WithdrawParams memory params_ = _withdrawStreams[i]; - _executeWithdrawByDelegation(params_.delegations, params_.token, params_.shareAmount, params_.minimumAssets, caller_); + _executeWithdrawByDelegation(params_.delegations, params_.token, params_.minimumAssets, caller_); unchecked { ++i; } @@ -295,16 +309,15 @@ contract VedaAdapter is Ownable2Step { /** * @notice Internal implementation of deposit by delegation + * @dev Parses the deposit token and amount from the first caveat of the leaf delegation + * (`_delegations[0].caveats[0].terms`), which must be 52 bytes in + * ERC20TransferAmountEnforcer format: abi.encodePacked(address token, uint256 amount). * @param _delegations Delegation chain, sorted leaf to root - * @param _token Token to deposit - * @param _amount Amount to deposit - * @param _minimumMint Minimum vault shares expected + * @param _minimumMint Minimum vault shares expected (sanity-check bound) * @param _caller Address of the caller, used only for event emission */ function _executeDepositByDelegation( Delegation[] memory _delegations, - address _token, - uint256 _amount, uint256 _minimumMint, address _caller ) @@ -312,7 +325,21 @@ contract VedaAdapter is Ownable2Step { { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); - if (_token == address(0)) revert InvalidZeroAddress(); + + // Parse token and amount from the leaf delegation's first caveat terms. + // Terms format (ERC20TransferAmountEnforcer): abi.encodePacked(address token, uint256 amount) = 52 bytes. + // Slice syntax is only available for calldata; use assembly to read from memory bytes. + bytes memory terms_ = _delegations[0].caveats[0].terms; + address token_; + uint256 amount_; + assembly { + // Memory layout of `terms_`: [length (32 bytes)][data ...]. + // `add(terms_, 32)` points to byte 0 of the data. + // The address occupies bytes 0-19 (high 20 bytes of the first 32-byte word). + token_ := shr(96, mload(add(terms_, 32))) + // The uint256 occupies bytes 20-51; load the 32-byte word starting at byte 20. + amount_ := mload(add(terms_, 52)) + } address rootDelegator_ = _delegations[length_ - 1].delegator; @@ -324,30 +351,32 @@ contract VedaAdapter is Ownable2Step { encodedModes_[0] = ModeLib.encodeSimpleSingle(); bytes[] memory executionCallDatas_ = new bytes[](1); - bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), _amount)); - executionCallDatas_[0] = ExecutionLib.encodeSingle(_token, 0, encodedTransfer_); + bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), amount_)); + executionCallDatas_[0] = ExecutionLib.encodeSingle(token_, 0, encodedTransfer_); delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); // Approve BoringVault to pull tokens, then deposit via Teller - _ensureAllowance(IERC20(_token), boringVault, _amount); - uint256 shares_ = teller.deposit(_token, _amount, _minimumMint, rootDelegator_, address(0)); + _ensureAllowance(IERC20(token_), boringVault, amount_); + uint256 shares_ = teller.deposit(token_, amount_, _minimumMint, rootDelegator_, address(0)); - emit DepositExecuted(rootDelegator_, _caller, _token, _amount, shares_); + emit DepositExecuted(rootDelegator_, _caller, token_, amount_, shares_); } /** * @notice Internal implementation of withdraw by delegation + * @dev Parses the share amount from the first caveat of the leaf delegation + * (`_delegations[0].caveats[0].terms`), which must be 52 bytes in + * ERC20TransferAmountEnforcer format: abi.encodePacked(address boringVault, uint256 shareAmount). + * The transfer target is always the immutable `boringVault` address. * @param _delegations Delegation chain, sorted leaf to root - * @param _token Underlying token to receive - * @param _shareAmount Amount of vault shares to redeem - * @param _minimumAssets Minimum underlying assets expected + * @param _token Underlying token to receive from the vault (not in the caveat; differs from the share token) + * @param _minimumAssets Minimum underlying assets expected (sanity-check bound) * @param _caller Address of the caller, used only for event emission */ function _executeWithdrawByDelegation( Delegation[] memory _delegations, address _token, - uint256 _shareAmount, uint256 _minimumAssets, address _caller ) @@ -357,6 +386,16 @@ contract VedaAdapter is Ownable2Step { if (length_ < 2) revert InvalidDelegationsLength(); if (_token == address(0)) revert InvalidZeroAddress(); + // Parse share amount from the leaf delegation's first caveat terms. + // Terms format (ERC20TransferAmountEnforcer): abi.encodePacked(address boringVault, uint256 shareAmount) = 52 bytes. + // Slice syntax is only available for calldata; use assembly to read from memory bytes. + bytes memory terms_ = _delegations[0].caveats[0].terms; + uint256 shareAmount_; + assembly { + // The uint256 shareAmount occupies bytes 20-51; load the 32-byte word starting at byte 20. + shareAmount_ := mload(add(terms_, 52)) + } + address rootDelegator_ = _delegations[length_ - 1].delegator; // Redeem delegation: transfer vault shares from user to this adapter @@ -367,14 +406,14 @@ contract VedaAdapter is Ownable2Step { encodedModes_[0] = ModeLib.encodeSimpleSingle(); bytes[] memory executionCallDatas_ = new bytes[](1); - bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), _shareAmount)); + bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), shareAmount_)); executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, encodedTransfer_); delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); // Withdraw from Teller: burns shares from this adapter, sends underlying to root delegator - uint256 assetsOut_ = teller.withdraw(_token, _shareAmount, _minimumAssets, rootDelegator_); + uint256 assetsOut_ = teller.withdraw(_token, shareAmount_, _minimumAssets, rootDelegator_); - emit WithdrawExecuted(rootDelegator_, _caller, _token, _shareAmount, assetsOut_); + emit WithdrawExecuted(rootDelegator_, _caller, _token, shareAmount_, assetsOut_); } } diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index 59d3aea9..bba13543 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -188,7 +188,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.depositByDelegation(delegations_, 0); uint256 aliceUSDCFinal_ = USDC.balanceOf(address(users.alice.deleGator)); assertEq(aliceUSDCFinal_, INITIAL_USD_BALANCE - DEPOSIT_AMOUNT, "USDC balance should decrease"); @@ -219,7 +219,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), aliceShares_, 0); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); assertEq(aliceSharesAfter_, 0, "All shares should be burned"); @@ -279,7 +279,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.depositByDelegation(delegations_, 0); } /// @notice depositByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) @@ -291,43 +291,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); - } - - /// @notice depositByDelegation must revert when token address is zero - function test_depositByDelegation_revertsOnZeroTokenAddress() public { - Delegation memory delegation_ = - _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), DEPOSIT_AMOUNT); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(0), DEPOSIT_AMOUNT, 0); - } - - /// @notice Depositing more than the delegation's ERC20TransferAmountEnforcer cap must revert - function test_depositByDelegation_revertsOnExcessiveAmount() public { - Delegation memory delegation_ = - _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - uint256 excessiveAmount_ = DEPOSIT_AMOUNT + 1; - vm.expectRevert(); - vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), excessiveAmount_, 0); - - // Verify no state change - assertEq(USDC.balanceOf(address(users.alice.deleGator)), INITIAL_USD_BALANCE); + vedaAdapter.depositByDelegation(delegations_, 0); } // ================================================================================== @@ -343,7 +307,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); } /// @notice withdrawByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) @@ -357,10 +321,10 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); } - /// @notice withdrawByDelegation must revert when token address is zero + /// @notice withdrawByDelegation must revert when underlying token address is zero function test_withdrawByDelegation_revertsOnZeroTokenAddress() public { _setupLendingState(); @@ -376,7 +340,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(0), DEPOSIT_AMOUNT, 0); + vedaAdapter.withdrawByDelegation(delegations_, address(0), 0); } // ================================================================================== @@ -402,7 +366,7 @@ contract VedaLendingTest is BaseTest { ); vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.depositByDelegation(delegations_, 0); } /// @notice withdrawByDelegation must emit WithdrawExecuted with correct parameters @@ -429,7 +393,7 @@ contract VedaLendingTest is BaseTest { ); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), aliceShares_, 0); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); } // ================================================================================== @@ -481,10 +445,8 @@ contract VedaLendingTest is BaseTest { delegations2_[1] = delegation2_; VedaAdapter.DepositParams[] memory streams_ = new VedaAdapter.DepositParams[](2); - streams_[0] = - VedaAdapter.DepositParams({ delegations: delegations1_, token: address(USDC), amount: amount1_, minimumMint: 0 }); - streams_[1] = - VedaAdapter.DepositParams({ delegations: delegations2_, token: address(USDC), amount: amount2_, minimumMint: 0 }); + streams_[0] = VedaAdapter.DepositParams({ delegations: delegations1_, minimumMint: 0 }); + streams_[1] = VedaAdapter.DepositParams({ delegations: delegations2_, minimumMint: 0 }); vm.expectEmit(true, true, true, true, address(vedaAdapter)); emit VedaAdapter.BatchDepositExecuted(address(users.bob.deleGator), 2); @@ -592,7 +554,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.depositByDelegation(delegations_, 0); assertEq(USDC.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any USDC after deposit"); } @@ -615,7 +577,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), aliceShares_, 0); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); } @@ -635,7 +597,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.depositByDelegation(delegations_, 0); assertEq( USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), @@ -675,7 +637,7 @@ contract VedaLendingTest is BaseTest { delegations_[2] = rootDelegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); + vedaAdapter.depositByDelegation(delegations_, 0); // rootDelegator_ = delegations[2].delegator = Alice uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); @@ -685,26 +647,6 @@ contract VedaLendingTest is BaseTest { assertEq(BORING_VAULT.balanceOf(address(users.bob.deleGator)), 0, "Bob must not receive shares"); } - /// @notice Passing a token to depositByDelegation that differs from the delegation enforcer's - /// token must revert, because the transfer calldata won't match the enforcer's terms. - function test_depositByDelegation_revertsOnTokenMismatch() public { - // Delegation enforcer is set up for BORING_VAULT (share token), but we try to deposit USDC - Delegation memory delegation_ = _createTransferDelegation( - address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max - ); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - // The adapter will try to transfer USDC, but the enforcer only allows BORING_VAULT token transfers - vm.expectRevert(); - vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0); - } - // ================================================================================== // Helper Functions // ================================================================================== @@ -729,7 +671,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.depositByDelegation(delegations_, address(USDC), _amount, 0); + vedaAdapter.depositByDelegation(delegations_, 0); } /// @notice Builds a WithdrawParams struct for batch withdraw (helper to reduce stack depth) @@ -743,9 +685,7 @@ contract VedaLendingTest is BaseTest { wdDelegations_[0] = rewd_; wdDelegations_[1] = wd_; - return VedaAdapter.WithdrawParams({ - delegations: wdDelegations_, token: address(USDC), shareAmount: _shareAmount, minimumAssets: 0 - }); + return VedaAdapter.WithdrawParams({ delegations: wdDelegations_, token: address(USDC), minimumAssets: 0 }); } /// @notice Creates a transfer delegation with ERC20TransferAmountEnforcer and RedeemerEnforcer From 57120d537aa5c3884d855306a5783295cc7e6087 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 2 Apr 2026 09:39:27 +0200 Subject: [PATCH 10/23] additional tests and code optimization --- src/helpers/VedaAdapter.sol | 57 ++++---- test/helpers/VedaLending.t.sol | 259 ++++++++++++++++++++++++++++++++- 2 files changed, 278 insertions(+), 38 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index d3a423bd..512a4e45 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -140,6 +140,9 @@ contract VedaAdapter is Ownable2Step { /// @dev Thrown when the batch array is empty error InvalidBatchLength(); + /// @dev Thrown when the leaf caveat terms are shorter than 52 bytes (ERC20TransferAmountEnforcer format) + error InvalidTermsLength(); + ////////////////////////////// State ////////////////////////////// /** @@ -307,11 +310,26 @@ contract VedaAdapter is Ownable2Step { } } + /** + * @notice Parses ERC20TransferAmountEnforcer terms from memory bytes + * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes. + * Slice syntax is only available for calldata; assembly is used to read from memory bytes. + * @param _terms The raw terms bytes from a caveat + * @return token_ The token address encoded in the first 20 bytes + * @return amount_ The uint256 amount encoded in bytes 20-51 + */ + function _parseERC20TransferTerms(bytes memory _terms) private pure returns (address token_, uint256 amount_) { + if (_terms.length < 52) revert InvalidTermsLength(); + assembly { + token_ := shr(96, mload(add(_terms, 32))) + amount_ := mload(add(_terms, 52)) + } + } + /** * @notice Internal implementation of deposit by delegation * @dev Parses the deposit token and amount from the first caveat of the leaf delegation - * (`_delegations[0].caveats[0].terms`), which must be 52 bytes in - * ERC20TransferAmountEnforcer format: abi.encodePacked(address token, uint256 amount). + * via `_parseERC20TransferTerms`. * @param _delegations Delegation chain, sorted leaf to root * @param _minimumMint Minimum vault shares expected (sanity-check bound) * @param _caller Address of the caller, used only for event emission @@ -326,21 +344,7 @@ contract VedaAdapter is Ownable2Step { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); - // Parse token and amount from the leaf delegation's first caveat terms. - // Terms format (ERC20TransferAmountEnforcer): abi.encodePacked(address token, uint256 amount) = 52 bytes. - // Slice syntax is only available for calldata; use assembly to read from memory bytes. - bytes memory terms_ = _delegations[0].caveats[0].terms; - address token_; - uint256 amount_; - assembly { - // Memory layout of `terms_`: [length (32 bytes)][data ...]. - // `add(terms_, 32)` points to byte 0 of the data. - // The address occupies bytes 0-19 (high 20 bytes of the first 32-byte word). - token_ := shr(96, mload(add(terms_, 32))) - // The uint256 occupies bytes 20-51; load the 32-byte word starting at byte 20. - amount_ := mload(add(terms_, 52)) - } - + (address token_, uint256 amount_) = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); address rootDelegator_ = _delegations[length_ - 1].delegator; // Redeem delegation: transfer tokens from user to this adapter @@ -366,11 +370,11 @@ contract VedaAdapter is Ownable2Step { /** * @notice Internal implementation of withdraw by delegation * @dev Parses the share amount from the first caveat of the leaf delegation - * (`_delegations[0].caveats[0].terms`), which must be 52 bytes in - * ERC20TransferAmountEnforcer format: abi.encodePacked(address boringVault, uint256 shareAmount). - * The transfer target is always the immutable `boringVault` address. + * via `_parseERC20TransferTerms`. The caveat encodes the vault share token and amount; + * `_token` is the desired underlying output asset (e.g. USDC), which differs from the + * share token in the caveat. * @param _delegations Delegation chain, sorted leaf to root - * @param _token Underlying token to receive from the vault (not in the caveat; differs from the share token) + * @param _token Underlying output token to receive from the vault (differs from the share token in the caveat) * @param _minimumAssets Minimum underlying assets expected (sanity-check bound) * @param _caller Address of the caller, used only for event emission */ @@ -386,16 +390,7 @@ contract VedaAdapter is Ownable2Step { if (length_ < 2) revert InvalidDelegationsLength(); if (_token == address(0)) revert InvalidZeroAddress(); - // Parse share amount from the leaf delegation's first caveat terms. - // Terms format (ERC20TransferAmountEnforcer): abi.encodePacked(address boringVault, uint256 shareAmount) = 52 bytes. - // Slice syntax is only available for calldata; use assembly to read from memory bytes. - bytes memory terms_ = _delegations[0].caveats[0].terms; - uint256 shareAmount_; - assembly { - // The uint256 shareAmount occupies bytes 20-51; load the 32-byte word starting at byte 20. - shareAmount_ := mload(add(terms_, 52)) - } - + (, uint256 shareAmount_) = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); address rootDelegator_ = _delegations[length_ - 1].delegator; // Redeem delegation: transfer vault shares from user to this adapter diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index bba13543..eaeaa69a 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -9,7 +9,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { EncoderLib } from "../../src/libraries/EncoderLib.sol"; import { IVedaTeller } from "../../src/helpers/interfaces/IVedaTeller.sol"; import { BaseTest } from "../utils/BaseTest.t.sol"; -import { Implementation, SignatureType } from "../utils/Types.t.sol"; +import { Implementation, SignatureType, TestUser } from "../utils/Types.t.sol"; import { Execution, Delegation, Caveat, ModeCode, CallType, ExecType } from "../../src/utils/Types.sol"; import { CALLTYPE_BATCH, EXECTYPE_TRY, MODE_DEFAULT } from "../../src/utils/Constants.sol"; import { ModePayload } from "@erc7579/lib/ModeLib.sol"; @@ -647,6 +647,220 @@ contract VedaLendingTest is BaseTest { assertEq(BORING_VAULT.balanceOf(address(users.bob.deleGator)), 0, "Bob must not receive shares"); } + // ================================================================================== + // Section 10: Terms Validation Tests + // Ensures the adapter rejects malformed caveat terms before executing. + // ================================================================================== + + /// @notice depositByDelegation must revert when leaf caveat terms are shorter than 52 bytes + function test_depositByDelegation_revertsOnShortTerms() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + + Caveat[] memory shortCaveats_ = new Caveat[](1); + shortCaveats_[0] = Caveat({ + args: hex"", + enforcer: address(erc20TransferAmountEnforcer), + terms: abi.encodePacked(address(USDC)) // 20 bytes, too short + }); + + Delegation memory redelegation_ = Delegation({ + delegate: address(vedaAdapter), + delegator: address(users.bob.deleGator), + authority: EncoderLib._getDelegationHash(delegation_), + caveats: shortCaveats_, + salt: 0, + signature: hex"" + }); + redelegation_ = signDelegation(users.bob, redelegation_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidTermsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + /// @notice withdrawByDelegation must revert when leaf caveat terms are shorter than 52 bytes + function test_withdrawByDelegation_revertsOnShortTerms() public { + _setupLendingState(); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + + Caveat[] memory shortCaveats_ = new Caveat[](1); + shortCaveats_[0] = Caveat({ + args: hex"", + enforcer: address(erc20TransferAmountEnforcer), + terms: hex"aabbccdd" // 4 bytes, too short + }); + + Delegation memory redelegation_ = Delegation({ + delegate: address(vedaAdapter), + delegator: address(users.bob.deleGator), + authority: EncoderLib._getDelegationHash(delegation_), + caveats: shortCaveats_, + salt: 0, + signature: hex"" + }); + redelegation_ = signDelegation(users.bob, redelegation_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.expectRevert(VedaAdapter.InvalidTermsLength.selector); + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + } + + // ================================================================================== + // Section 11: Replay / Double-Spend Prevention Tests + // Validates that the ERC20TransferAmountEnforcer prevents reuse of the same delegation. + // ================================================================================== + + /// @notice Calling depositByDelegation twice with the same delegation chain must revert on the second call + function test_depositByDelegation_revertsOnReplay() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(); + vedaAdapter.depositByDelegation(delegations_, 0); + } + + // ================================================================================== + // Section 12: Slippage Protection Tests + // Validates that minimumMint / minimumAssets bounds cause reverts when not met. + // ================================================================================== + + /// @notice depositByDelegation must revert when minimumMint exceeds the actual shares minted + function test_depositByDelegation_revertsOnSlippage() public { + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(); + vedaAdapter.depositByDelegation(delegations_, type(uint256).max); + } + + /// @notice withdrawByDelegation must revert when minimumAssets exceeds the actual assets received + function test_withdrawByDelegation_revertsOnSlippage() public { + _setupLendingState(); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 aliceShares_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); + + Delegation memory delegation_ = _createTransferDelegation( + address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max + ); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), aliceShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vm.expectRevert(); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), type(uint256).max); + } + + // ================================================================================== + // Section 13: Alternative Delegator Tests + // Validates the adapter works correctly when Carol (not Alice) is the root delegator. + // ================================================================================== + + /// @notice Deposit via adapter where Carol is the root delegator instead of Alice + function test_depositByDelegation_carolAsRootDelegator() public { + vm.deal(address(users.carol.deleGator), 1 ether); + vm.prank(USDC_WHALE); + USDC.transfer(address(users.carol.deleGator), INITIAL_USD_BALANCE); + + uint256 carolUSDCBefore_ = USDC.balanceOf(address(users.carol.deleGator)); + + // Carol delegates USDC transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = _createTransferDelegationFull( + users.carol, address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max, 0 + ); + + // Bob redelegates to the VedaAdapter with a transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + uint256 carolUSDCAfter_ = USDC.balanceOf(address(users.carol.deleGator)); + assertEq(carolUSDCAfter_, carolUSDCBefore_ - DEPOSIT_AMOUNT, "Carol's USDC should decrease"); + + uint256 carolShares_ = BORING_VAULT.balanceOf(address(users.carol.deleGator)); + assertGt(carolShares_, 0, "Shares should be minted to Carol (root delegator)"); + + assertEq(BORING_VAULT.balanceOf(address(users.bob.deleGator)), 0, "Bob must not receive shares"); + assertEq(BORING_VAULT.balanceOf(address(users.alice.deleGator)), 0, "Alice must not receive shares"); + } + + /// @notice Withdraw via adapter where Carol is the root delegator instead of Alice + function test_withdrawByDelegation_carolAsRootDelegator() public { + vm.deal(address(users.carol.deleGator), 1 ether); + vm.prank(USDC_WHALE); + USDC.transfer(address(users.carol.deleGator), INITIAL_USD_BALANCE); + + // Carol deposits directly to get shares + vm.prank(address(users.carol.deleGator)); + USDC.approve(address(BORING_VAULT), DEPOSIT_AMOUNT); + vm.prank(address(users.carol.deleGator)); + VEDA_TELLER.deposit(address(USDC), DEPOSIT_AMOUNT, 0, address(0)); + vm.warp(block.timestamp + SHARE_LOCK_SECONDS); + + uint256 carolShares_ = BORING_VAULT.balanceOf(address(users.carol.deleGator)); + assertGt(carolShares_, 0, "Carol should have vault shares"); + uint256 carolUSDCBefore_ = USDC.balanceOf(address(users.carol.deleGator)); + + // Carol delegates share transfer rights to Bob, redeemable only by the adapter + Delegation memory delegation_ = _createTransferDelegationFull( + users.carol, address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max, 0 + ); + + // Bob redelegates to the VedaAdapter with a share transfer amount cap + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), carolShares_); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + + assertEq(BORING_VAULT.balanceOf(address(users.carol.deleGator)), 0, "All shares should be burned"); + uint256 carolUSDCAfter_ = USDC.balanceOf(address(users.carol.deleGator)); + assertGt(carolUSDCAfter_, carolUSDCBefore_, "Carol should receive USDC back"); + } + // ================================================================================== // Helper Functions // ================================================================================== @@ -699,7 +913,7 @@ contract VedaLendingTest is BaseTest { view returns (Delegation memory) { - return _createTransferDelegationWithSalt(_delegate, _redeemer, _token, _amount, 0); + return _createTransferDelegationFull(users.alice, _delegate, _redeemer, _token, _amount, 0); } /// @notice Creates a transfer delegation with a custom salt for unique delegation hashes in batch operations @@ -713,6 +927,22 @@ contract VedaLendingTest is BaseTest { internal view returns (Delegation memory) + { + return _createTransferDelegationFull(users.alice, _delegate, _redeemer, _token, _amount, _salt); + } + + /// @notice Creates a transfer delegation signed by an arbitrary delegator + function _createTransferDelegationFull( + TestUser memory _delegator, + address _delegate, + address _redeemer, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) { Caveat[] memory caveats_ = new Caveat[](2); caveats_[0] = @@ -722,14 +952,14 @@ contract VedaLendingTest is BaseTest { Delegation memory delegation_ = Delegation({ delegate: _delegate, - delegator: address(users.alice.deleGator), + delegator: address(_delegator.deleGator), authority: ROOT_AUTHORITY, caveats: caveats_, salt: _salt, signature: hex"" }); - return signDelegation(users.alice, delegation_); + return signDelegation(_delegator, delegation_); } /// @notice Creates an adapter redelegation with ERC20TransferAmountEnforcer @@ -742,7 +972,7 @@ contract VedaLendingTest is BaseTest { view returns (Delegation memory) { - return _createAdapterRedelegationWithSalt(_authority, _token, _amount, 0); + return _createAdapterRedelegationFull(users.bob, _authority, _token, _amount, 0); } /// @notice Creates an adapter redelegation with a custom salt for unique delegation hashes in batch operations @@ -755,6 +985,21 @@ contract VedaLendingTest is BaseTest { internal view returns (Delegation memory) + { + return _createAdapterRedelegationFull(users.bob, _authority, _token, _amount, _salt); + } + + /// @notice Creates an adapter redelegation signed by an arbitrary operator + function _createAdapterRedelegationFull( + TestUser memory _operator, + bytes32 _authority, + address _token, + uint256 _amount, + uint256 _salt + ) + internal + view + returns (Delegation memory) { Caveat[] memory caveats_ = new Caveat[](1); caveats_[0] = @@ -762,13 +1007,13 @@ contract VedaLendingTest is BaseTest { Delegation memory delegation_ = Delegation({ delegate: address(vedaAdapter), - delegator: address(users.bob.deleGator), + delegator: address(_operator.deleGator), authority: _authority, caveats: caveats_, salt: _salt, signature: hex"" }); - return signDelegation(users.bob, delegation_); + return signDelegation(_operator, delegation_); } } From 4deb901199580a2b4572ce879a13192b1c4afdd1 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 2 Apr 2026 10:14:33 +0200 Subject: [PATCH 11/23] linter + fetching from latest block --- src/helpers/VedaAdapter.sol | 16 ++-------------- test/helpers/VedaLending.t.sol | 3 +-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 512a4e45..402edeff 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -243,13 +243,7 @@ contract VedaAdapter is Ownable2Step { * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an * `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay. */ - function withdrawByDelegation( - Delegation[] memory _delegations, - address _token, - uint256 _minimumAssets - ) - external - { + function withdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _minimumAssets) external { _executeWithdrawByDelegation(_delegations, _token, _minimumAssets, msg.sender); } @@ -334,13 +328,7 @@ contract VedaAdapter is Ownable2Step { * @param _minimumMint Minimum vault shares expected (sanity-check bound) * @param _caller Address of the caller, used only for event emission */ - function _executeDepositByDelegation( - Delegation[] memory _delegations, - uint256 _minimumMint, - address _caller - ) - internal - { + function _executeDepositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint, address _caller) internal { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index eaeaa69a..1bd38e43 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -68,7 +68,6 @@ contract VedaLendingTest is BaseTest { LimitedCallsEnforcer public limitedCallsEnforcer; VedaAdapter public vedaAdapter; - uint256 public constant MAINNET_FORK_BLOCK = 447148700; // Use latest available block uint256 public constant INITIAL_USD_BALANCE = 10000000000; // 10k USDC uint256 public constant DEPOSIT_AMOUNT = 1000000000; // 1k USDC uint256 public constant SHARE_LOCK_SECONDS = 61; // Warp past the 60s share lock period applied by deposit() @@ -77,7 +76,7 @@ contract VedaLendingTest is BaseTest { function setUp() public override { // Create fork from mainnet at specific block - vm.createSelectFork(vm.envString("ARBITRUM_RPC_URL"), MAINNET_FORK_BLOCK); + vm.createSelectFork(vm.envString("ARBITRUM_RPC_URL")); // Set implementation type IMPLEMENTATION = Implementation.Hybrid; From 57b5b88c10f5a5a64163f084c2c97532a11f63b7 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Sun, 5 Apr 2026 20:05:01 +0200 Subject: [PATCH 12/23] move deploy script to .env variables --- .env.example | 3 +++ script/DeployVedaAdapter.s.sol | 27 ++++++++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index 390adce3..df0cd20b 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,9 @@ META_SWAP_ADAPTER_OWNER_ADDRESS= METASWAP_ADDRESS= SWAPS_API_SIGNER_ADDRESS= ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS= +VEDA_ADAPTER_OWNER_ADDRESS= +BORING_VAULT_ADDRESS= +VEDA_TELLER_ADDRESS= # Required for verifying contracts ETHERSCAN_API_KEY= diff --git a/script/DeployVedaAdapter.s.sol b/script/DeployVedaAdapter.s.sol index cf63862f..b9b1c145 100644 --- a/script/DeployVedaAdapter.s.sol +++ b/script/DeployVedaAdapter.s.sol @@ -9,29 +9,30 @@ import { VedaAdapter } from "../src/helpers/VedaAdapter.sol"; /** * @title DeployVedaAdapter * @notice Deploys the VedaAdapter contract. - * @dev Update the hardcoded addresses below before deploying. - * @dev Fill the SALT variable in the .env file + * @dev Fill the required variables in the .env file * @dev run the script with: * forge script script/DeployVedaAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast */ contract DeployVedaAdapter is Script { - // Hardcoded constructor parameters - update these before deploying - address constant OWNER = address(0x0000000000000000000000000000000000000000); - address constant DELEGATION_MANAGER = address(0x0000000000000000000000000000000000000000); - address constant BORING_VAULT = address(0x0000000000000000000000000000000000000000); - address constant VEDA_TELLER = address(0x0000000000000000000000000000000000000000); - bytes32 salt; address deployer; + address vedaAdapterOwner; + address delegationManager; + address boringVault; + address vedaTeller; function setUp() public { salt = bytes32(abi.encodePacked(vm.envString("SALT"))); + vedaAdapterOwner = vm.envAddress("VEDA_ADAPTER_OWNER_ADDRESS"); + delegationManager = vm.envAddress("DELEGATION_MANAGER_ADDRESS"); + boringVault = vm.envAddress("BORING_VAULT_ADDRESS"); + vedaTeller = vm.envAddress("VEDA_TELLER_ADDRESS"); deployer = msg.sender; console2.log("~~~"); - console2.log("Owner: %s", OWNER); - console2.log("DelegationManager: %s", DELEGATION_MANAGER); - console2.log("BoringVault: %s", BORING_VAULT); - console2.log("VedaTeller: %s", VEDA_TELLER); + console2.log("Owner: %s", vedaAdapterOwner); + console2.log("DelegationManager: %s", delegationManager); + console2.log("BoringVault: %s", boringVault); + console2.log("VedaTeller: %s", vedaTeller); console2.log("Deployer: %s", deployer); console2.log("Salt:"); console2.logBytes32(salt); @@ -41,7 +42,7 @@ contract DeployVedaAdapter is Script { console2.log("~~~"); vm.startBroadcast(); - address vedaAdapter = address(new VedaAdapter{ salt: salt }(OWNER, DELEGATION_MANAGER, BORING_VAULT, VEDA_TELLER)); + address vedaAdapter = address(new VedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller)); console2.log("VedaAdapter: %s", vedaAdapter); vm.stopBroadcast(); From e412baf643791b38994d83af85034c49cc4259fb Mon Sep 17 00:00:00 2001 From: MoMannn Date: Sun, 5 Apr 2026 20:05:11 +0200 Subject: [PATCH 13/23] fix teller docs --- src/helpers/interfaces/IVedaTeller.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/helpers/interfaces/IVedaTeller.sol b/src/helpers/interfaces/IVedaTeller.sol index 986e84c6..ab9a1d8d 100644 --- a/src/helpers/interfaces/IVedaTeller.sol +++ b/src/helpers/interfaces/IVedaTeller.sol @@ -35,6 +35,12 @@ interface IVedaTeller { /** * @notice Allows an authorized caller to deposit into the BoringVault for another address, if this contract is not paused. * @dev Intended for router-like integrations; this selector should remain role-gated. + * @param depositAsset The ERC20 token to deposit + * @param depositAmount The amount to deposit + * @param minimumMint The minimum shares the user expects to receive + * @param to The address that will receive the minted vault shares + * @param referralAddress Address used for referral tracking + * @return shares The number of vault shares minted */ function deposit( address depositAsset, From 801ab0d8b47521dbbd21d69d94530f50acd37afa Mon Sep 17 00:00:00 2001 From: MoMannn Date: Sun, 5 Apr 2026 20:05:26 +0200 Subject: [PATCH 14/23] remove unnecesarry caller variable --- src/helpers/VedaAdapter.sol | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 402edeff..671b9769 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -199,7 +199,7 @@ contract VedaAdapter is Ownable2Step { * over-spending or replay. */ function depositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint) external { - _executeDepositByDelegation(_delegations, _minimumMint, msg.sender); + _executeDepositByDelegation(_delegations, _minimumMint); } /** @@ -214,16 +214,15 @@ contract VedaAdapter is Ownable2Step { uint256 streamsLength_ = _depositStreams.length; if (streamsLength_ == 0) revert InvalidBatchLength(); - address caller_ = msg.sender; for (uint256 i = 0; i < streamsLength_;) { DepositParams memory params_ = _depositStreams[i]; - _executeDepositByDelegation(params_.delegations, params_.minimumMint, caller_); + _executeDepositByDelegation(params_.delegations, params_.minimumMint); unchecked { ++i; } } - emit BatchDepositExecuted(caller_, streamsLength_); + emit BatchDepositExecuted(msg.sender, streamsLength_); } /** @@ -244,7 +243,7 @@ contract VedaAdapter is Ownable2Step { * `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay. */ function withdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _minimumAssets) external { - _executeWithdrawByDelegation(_delegations, _token, _minimumAssets, msg.sender); + _executeWithdrawByDelegation(_delegations, _token, _minimumAssets); } /** @@ -259,16 +258,15 @@ contract VedaAdapter is Ownable2Step { uint256 streamsLength_ = _withdrawStreams.length; if (streamsLength_ == 0) revert InvalidBatchLength(); - address caller_ = msg.sender; for (uint256 i = 0; i < streamsLength_;) { WithdrawParams memory params_ = _withdrawStreams[i]; - _executeWithdrawByDelegation(params_.delegations, params_.token, params_.minimumAssets, caller_); + _executeWithdrawByDelegation(params_.delegations, params_.token, params_.minimumAssets); unchecked { ++i; } } - emit BatchWithdrawExecuted(caller_, streamsLength_); + emit BatchWithdrawExecuted(msg.sender, streamsLength_); } /** @@ -326,9 +324,8 @@ contract VedaAdapter is Ownable2Step { * via `_parseERC20TransferTerms`. * @param _delegations Delegation chain, sorted leaf to root * @param _minimumMint Minimum vault shares expected (sanity-check bound) - * @param _caller Address of the caller, used only for event emission */ - function _executeDepositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint, address _caller) internal { + function _executeDepositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint) internal { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); @@ -352,7 +349,7 @@ contract VedaAdapter is Ownable2Step { _ensureAllowance(IERC20(token_), boringVault, amount_); uint256 shares_ = teller.deposit(token_, amount_, _minimumMint, rootDelegator_, address(0)); - emit DepositExecuted(rootDelegator_, _caller, token_, amount_, shares_); + emit DepositExecuted(rootDelegator_, msg.sender, token_, amount_, shares_); } /** @@ -364,16 +361,8 @@ contract VedaAdapter is Ownable2Step { * @param _delegations Delegation chain, sorted leaf to root * @param _token Underlying output token to receive from the vault (differs from the share token in the caveat) * @param _minimumAssets Minimum underlying assets expected (sanity-check bound) - * @param _caller Address of the caller, used only for event emission */ - function _executeWithdrawByDelegation( - Delegation[] memory _delegations, - address _token, - uint256 _minimumAssets, - address _caller - ) - internal - { + function _executeWithdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _minimumAssets) internal { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); if (_token == address(0)) revert InvalidZeroAddress(); @@ -397,6 +386,6 @@ contract VedaAdapter is Ownable2Step { // Withdraw from Teller: burns shares from this adapter, sends underlying to root delegator uint256 assetsOut_ = teller.withdraw(_token, shareAmount_, _minimumAssets, rootDelegator_); - emit WithdrawExecuted(rootDelegator_, _caller, _token, shareAmount_, assetsOut_); + emit WithdrawExecuted(rootDelegator_, msg.sender, _token, shareAmount_, assetsOut_); } } From 16edf2ab895b8a9d68e656f80114602392864266 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Sun, 5 Apr 2026 20:08:58 +0200 Subject: [PATCH 15/23] update docs --- src/helpers/VedaAdapter.sol | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 671b9769..6fd84993 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -51,10 +51,11 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended - * deposit or withdrawal amount. Once that amount is transferred the enforcer's running total is exhausted and - * any replay attempt will revert, making the delegation effectively single-use. A delegation without this - * enforcer (or with an amount larger than intended) could be exploited by any caller to transfer more tokens - * than authorised. + * deposit or withdrawal amount, and it MUST be the first caveat (`caveats[0]`) of that redelegation — the + * adapter reads token and amount directly from `_delegations[0].caveats[0].terms`. Once that amount is + * transferred the enforcer's running total is exhausted and any replay attempt will revert, making the + * delegation effectively single-use. A delegation without this enforcer as the first caveat (or with an amount + * larger than intended) could be exploited by any caller to transfer more tokens than authorised. */ contract VedaAdapter is Ownable2Step { using SafeERC20 for IERC20; @@ -195,8 +196,8 @@ contract VedaAdapter is Ownable2Step { * is negligible. A tolerance of 0.1-0.5% is recommended. If this check causes a revert, * no funds are lost — retry with a fresh quote. * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an - * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent - * over-spending or replay. + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * deposit amount, to prevent over-spending or replay. */ function depositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint) external { _executeDepositByDelegation(_delegations, _minimumMint); @@ -208,7 +209,8 @@ contract VedaAdapter is Ownable2Step { * from the first caveat of each stream's leaf delegation. * @param _depositStreams Array of deposit parameters * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an - * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent over-spending or replay. + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * deposit amount, to prevent over-spending or replay. */ function depositByDelegationBatch(DepositParams[] memory _depositStreams) external { uint256 streamsLength_ = _depositStreams.length; @@ -240,7 +242,8 @@ contract VedaAdapter is Ownable2Step { * yield streaming is negligible. A tolerance of 0.1-0.5% is recommended. If this check * causes a revert, no funds are lost — retry with a fresh quote. * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an - * `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay. + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly `_shareAmount`, + * to prevent over-spending or replay. */ function withdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _minimumAssets) external { _executeWithdrawByDelegation(_delegations, _token, _minimumAssets); @@ -252,7 +255,8 @@ contract VedaAdapter is Ownable2Step { * from the first caveat of each stream's leaf delegation. * @param _withdrawStreams Array of withdraw parameters * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an - * `ERC20TransferAmountEnforcer` capped to exactly the intended share amount to prevent over-spending or replay. + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * share amount, to prevent over-spending or replay. */ function withdrawByDelegationBatch(WithdrawParams[] memory _withdrawStreams) external { uint256 streamsLength_ = _withdrawStreams.length; From 51f165496949c9195696149d38f67d467d962314 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Sun, 5 Apr 2026 20:17:04 +0200 Subject: [PATCH 16/23] Update approval logic to unlimited --- src/helpers/VedaAdapter.sol | 4 ++-- test/helpers/VedaLending.t.sol | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 6fd84993..8dd567ef 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -294,7 +294,7 @@ contract VedaAdapter is Ownable2Step { /** * @notice Ensures sufficient token allowance for a spender to pull tokens - * @dev Checks current allowance and sets exact amount if insufficient, avoiding accumulation + * @dev Checks current allowance and increases to unlimited if insufficient * @param _token Token to manage allowance for * @param _spender Address that needs to spend the tokens * @param _amount Amount needed for the operation @@ -302,7 +302,7 @@ contract VedaAdapter is Ownable2Step { function _ensureAllowance(IERC20 _token, address _spender, uint256 _amount) private { uint256 allowance_ = _token.allowance(address(this), _spender); if (allowance_ < _amount) { - _token.forceApprove(_spender, _amount); + _token.safeIncreaseAllowance(_spender, type(uint256).max); } } diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index 1bd38e43..d502d602 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -581,8 +581,8 @@ contract VedaLendingTest is BaseTest { assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); } - /// @notice BoringVault must fully consume the allowance granted by the adapter during deposit. - /// Verifies that _ensureAllowance does not cause unbounded allowance accumulation. + /// @notice After the first deposit, the adapter grants unlimited allowance to BoringVault. + /// Subsequent deposits reuse the existing allowance without issuing a new approval. function test_allowanceFullyConsumedAfterDeposit() public { assertEq(USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), 0, "Initial allowance should be 0"); @@ -600,8 +600,8 @@ contract VedaLendingTest is BaseTest { assertEq( USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), - 0, - "Allowance must be fully consumed after deposit -- no residual accumulation" + type(uint256).max - DEPOSIT_AMOUNT, + "Allowance should be unlimited minus the deposited amount" ); } From 1f1182eb9eea88ac5b88d19a753350d9f8bdafb2 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 9 Apr 2026 11:15:09 +0200 Subject: [PATCH 17/23] Use calldata instead of memory for external function array parameters --- src/helpers/VedaAdapter.sol | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 8dd567ef..45726230 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -199,7 +199,7 @@ contract VedaAdapter is Ownable2Step { * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended * deposit amount, to prevent over-spending or replay. */ - function depositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint) external { + function depositByDelegation(Delegation[] calldata _delegations, uint256 _minimumMint) external { _executeDepositByDelegation(_delegations, _minimumMint); } @@ -212,12 +212,12 @@ contract VedaAdapter is Ownable2Step { * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended * deposit amount, to prevent over-spending or replay. */ - function depositByDelegationBatch(DepositParams[] memory _depositStreams) external { + function depositByDelegationBatch(DepositParams[] calldata _depositStreams) external { uint256 streamsLength_ = _depositStreams.length; if (streamsLength_ == 0) revert InvalidBatchLength(); for (uint256 i = 0; i < streamsLength_;) { - DepositParams memory params_ = _depositStreams[i]; + DepositParams calldata params_ = _depositStreams[i]; _executeDepositByDelegation(params_.delegations, params_.minimumMint); unchecked { ++i; @@ -245,7 +245,7 @@ contract VedaAdapter is Ownable2Step { * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly `_shareAmount`, * to prevent over-spending or replay. */ - function withdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _minimumAssets) external { + function withdrawByDelegation(Delegation[] calldata _delegations, address _token, uint256 _minimumAssets) external { _executeWithdrawByDelegation(_delegations, _token, _minimumAssets); } @@ -258,12 +258,12 @@ contract VedaAdapter is Ownable2Step { * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended * share amount, to prevent over-spending or replay. */ - function withdrawByDelegationBatch(WithdrawParams[] memory _withdrawStreams) external { + function withdrawByDelegationBatch(WithdrawParams[] calldata _withdrawStreams) external { uint256 streamsLength_ = _withdrawStreams.length; if (streamsLength_ == 0) revert InvalidBatchLength(); for (uint256 i = 0; i < streamsLength_;) { - WithdrawParams memory params_ = _withdrawStreams[i]; + WithdrawParams calldata params_ = _withdrawStreams[i]; _executeWithdrawByDelegation(params_.delegations, params_.token, params_.minimumAssets); unchecked { ++i; @@ -307,19 +307,16 @@ contract VedaAdapter is Ownable2Step { } /** - * @notice Parses ERC20TransferAmountEnforcer terms from memory bytes + * @notice Parses ERC20TransferAmountEnforcer terms from calldata bytes * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes. - * Slice syntax is only available for calldata; assembly is used to read from memory bytes. * @param _terms The raw terms bytes from a caveat * @return token_ The token address encoded in the first 20 bytes * @return amount_ The uint256 amount encoded in bytes 20-51 */ - function _parseERC20TransferTerms(bytes memory _terms) private pure returns (address token_, uint256 amount_) { + function _parseERC20TransferTerms(bytes calldata _terms) private pure returns (address token_, uint256 amount_) { if (_terms.length < 52) revert InvalidTermsLength(); - assembly { - token_ := shr(96, mload(add(_terms, 32))) - amount_ := mload(add(_terms, 52)) - } + token_ = address(bytes20(_terms[0:20])); + amount_ = uint256(bytes32(_terms[20:52])); } /** @@ -329,7 +326,7 @@ contract VedaAdapter is Ownable2Step { * @param _delegations Delegation chain, sorted leaf to root * @param _minimumMint Minimum vault shares expected (sanity-check bound) */ - function _executeDepositByDelegation(Delegation[] memory _delegations, uint256 _minimumMint) internal { + function _executeDepositByDelegation(Delegation[] calldata _delegations, uint256 _minimumMint) internal { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); @@ -366,7 +363,7 @@ contract VedaAdapter is Ownable2Step { * @param _token Underlying output token to receive from the vault (differs from the share token in the caveat) * @param _minimumAssets Minimum underlying assets expected (sanity-check bound) */ - function _executeWithdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _minimumAssets) internal { + function _executeWithdrawByDelegation(Delegation[] calldata _delegations, address _token, uint256 _minimumAssets) internal { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); if (_token == address(0)) revert InvalidZeroAddress(); From adb6c64e92f75bf9dacc5528ef0da9a74b6853b7 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 9 Apr 2026 11:16:16 +0200 Subject: [PATCH 18/23] Eliminate single-use encodedTransfer_ local variable --- src/helpers/VedaAdapter.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 45726230..0d066303 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -341,8 +341,7 @@ contract VedaAdapter is Ownable2Step { encodedModes_[0] = ModeLib.encodeSimpleSingle(); bytes[] memory executionCallDatas_ = new bytes[](1); - bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), amount_)); - executionCallDatas_[0] = ExecutionLib.encodeSingle(token_, 0, encodedTransfer_); + executionCallDatas_[0] = ExecutionLib.encodeSingle(token_, 0, abi.encodeCall(IERC20.transfer, (address(this), amount_))); delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); @@ -379,8 +378,7 @@ contract VedaAdapter is Ownable2Step { encodedModes_[0] = ModeLib.encodeSimpleSingle(); bytes[] memory executionCallDatas_ = new bytes[](1); - bytes memory encodedTransfer_ = abi.encodeCall(IERC20.transfer, (address(this), shareAmount_)); - executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, encodedTransfer_); + executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, abi.encodeCall(IERC20.transfer, (address(this), shareAmount_))); delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); From 231e1de25044afdd67154f51efdc216bee0df3cc Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 9 Apr 2026 12:09:49 +0200 Subject: [PATCH 19/23] Fix _ensureAllowance --- src/helpers/VedaAdapter.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 0d066303..60f2c366 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -294,7 +294,7 @@ contract VedaAdapter is Ownable2Step { /** * @notice Ensures sufficient token allowance for a spender to pull tokens - * @dev Checks current allowance and increases to unlimited if insufficient + * @dev Checks current allowance and sets it to max if insufficient. * @param _token Token to manage allowance for * @param _spender Address that needs to spend the tokens * @param _amount Amount needed for the operation @@ -302,7 +302,7 @@ contract VedaAdapter is Ownable2Step { function _ensureAllowance(IERC20 _token, address _spender, uint256 _amount) private { uint256 allowance_ = _token.allowance(address(this), _spender); if (allowance_ < _amount) { - _token.safeIncreaseAllowance(_spender, type(uint256).max); + _token.forceApprove(_spender, type(uint256).max); } } @@ -378,7 +378,8 @@ contract VedaAdapter is Ownable2Step { encodedModes_[0] = ModeLib.encodeSimpleSingle(); bytes[] memory executionCallDatas_ = new bytes[](1); - executionCallDatas_[0] = ExecutionLib.encodeSingle(boringVault, 0, abi.encodeCall(IERC20.transfer, (address(this), shareAmount_))); + executionCallDatas_[0] = + ExecutionLib.encodeSingle(boringVault, 0, abi.encodeCall(IERC20.transfer, (address(this), shareAmount_))); delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); From aba8aa550ee340718cca670290fcd42f90a1f610 Mon Sep 17 00:00:00 2001 From: MoMannn Date: Thu, 9 Apr 2026 16:19:47 +0200 Subject: [PATCH 20/23] fixate veda adapter deposit / withdrawal token in constructor --- .env.example | 3 +- script/DeployVedaAdapter.s.sol | 8 ++- src/helpers/VedaAdapter.sol | 98 +++++++++++++++++++--------------- test/helpers/VedaLending.t.sol | 57 ++++++++------------ 4 files changed, 87 insertions(+), 79 deletions(-) diff --git a/.env.example b/.env.example index 23093ba1..bd97ff91 100644 --- a/.env.example +++ b/.env.example @@ -9,8 +9,9 @@ METASWAP_ADDRESS= SWAPS_API_SIGNER_ADDRESS= ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS= VEDA_ADAPTER_OWNER_ADDRESS= -BORING_VAULT_ADDRESS= +VEDA_BORING_VAULT_ADDRESS= VEDA_TELLER_ADDRESS= +VEDA_DEPOSIT_TOKEN_ADDRESS= # Required for verifying contracts ETHERSCAN_API_KEY= diff --git a/script/DeployVedaAdapter.s.sol b/script/DeployVedaAdapter.s.sol index b9b1c145..1fa72062 100644 --- a/script/DeployVedaAdapter.s.sol +++ b/script/DeployVedaAdapter.s.sol @@ -20,19 +20,22 @@ contract DeployVedaAdapter is Script { address delegationManager; address boringVault; address vedaTeller; + address depositToken; function setUp() public { salt = bytes32(abi.encodePacked(vm.envString("SALT"))); vedaAdapterOwner = vm.envAddress("VEDA_ADAPTER_OWNER_ADDRESS"); delegationManager = vm.envAddress("DELEGATION_MANAGER_ADDRESS"); - boringVault = vm.envAddress("BORING_VAULT_ADDRESS"); + boringVault = vm.envAddress("VEDA_BORING_VAULT_ADDRESS"); vedaTeller = vm.envAddress("VEDA_TELLER_ADDRESS"); + depositToken = vm.envAddress("VEDA_DEPOSIT_TOKEN_ADDRESS"); deployer = msg.sender; console2.log("~~~"); console2.log("Owner: %s", vedaAdapterOwner); console2.log("DelegationManager: %s", delegationManager); console2.log("BoringVault: %s", boringVault); console2.log("VedaTeller: %s", vedaTeller); + console2.log("DepositToken: %s", depositToken); console2.log("Deployer: %s", deployer); console2.log("Salt:"); console2.logBytes32(salt); @@ -42,7 +45,8 @@ contract DeployVedaAdapter is Script { console2.log("~~~"); vm.startBroadcast(); - address vedaAdapter = address(new VedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller)); + address vedaAdapter = + address(new VedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller, depositToken)); console2.log("VedaAdapter: %s", vedaAdapter); vm.stopBroadcast(); diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 60f2c366..2f9c8f9f 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -23,6 +23,8 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * - Teller: The contract that orchestrates deposits/withdrawals. The adapter calls `teller.deposit()` * for deposits and `teller.withdraw()` for withdrawals (user-facing, no special * role needed). + * - depositToken: The single ERC20 token used for both deposits and withdrawals. Fixed at construction; + * deposits transfer this token into the vault, and withdrawals redeem vault shares back to this token. * * Delegation Flow: * 1. The user creates an initial delegation to an "operator" address (a DeleGator-upgraded account). @@ -37,7 +39,7 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * 3. For deposits: the adapter redeems the delegation chain, transfers tokens from the user to itself, * approves the BoringVault, and calls `teller.deposit()` to mint shares to the user. * For withdrawals: the adapter redeems the delegation chain, transfers vault shares from the user - * to itself, and calls `teller.withdraw()` to burn shares and send underlying assets to the user. + * to itself, and calls `teller.withdraw()` to burn shares and send `depositToken` assets to the user. * * Requirements: * - VedaAdapter must approve the BoringVault to spend deposit tokens @@ -45,14 +47,14 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * Leaf Caveat Format: * - The first caveat of the leaf delegation (`_delegations[0].caveats[0]`) must follow the * ERC20TransferAmountEnforcer terms format: abi.encodePacked(address token, uint256 amount) (52 bytes). - * The adapter parses token and amount directly from these terms instead of accepting them as - * function inputs, ensuring the caller cannot supply values that differ from what the delegator authorised. + * The adapter parses only the amount from these terms; the token address encoded in bytes 0–19 is + * consumed by the enforcer itself and is not read by this adapter. * * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended * deposit or withdrawal amount, and it MUST be the first caveat (`caveats[0]`) of that redelegation — the - * adapter reads token and amount directly from `_delegations[0].caveats[0].terms`. Once that amount is + * adapter reads the amount directly from `_delegations[0].caveats[0].terms`. Once that amount is * transferred the enforcer's running total is exhausted and any replay attempt will revert, making the * delegation effectively single-use. A delegation without this enforcer as the first caveat (or with an amount * larger than intended) could be exploited by any caller to transfer more tokens than authorised. @@ -75,7 +77,6 @@ contract VedaAdapter is Ownable2Step { */ struct WithdrawParams { Delegation[] delegations; - address token; uint256 minimumAssets; } @@ -97,7 +98,7 @@ contract VedaAdapter is Ownable2Step { * @notice Emitted when a withdrawal operation is executed via delegation * @param delegator Address of the share owner (delegator) * @param delegate Address of the executor (delegate) - * @param token Address of the underlying token withdrawn + * @param token Address of the underlying token withdrawn (always `depositToken`) * @param shareAmount Amount of vault shares burned * @param assetsOut Amount of underlying tokens sent to the delegator */ @@ -161,33 +162,50 @@ contract VedaAdapter is Ownable2Step { */ IVedaTeller public immutable teller; + /** + * @notice The ERC20 token used for all deposits and withdrawals + * @dev Fixed at construction. Deposits transfer this token into the vault; withdrawals redeem vault + * shares back to this token. + */ + IERC20 public immutable depositToken; + ////////////////////////////// Constructor ////////////////////////////// /** - * @notice Initializes the adapter with delegation manager, BoringVault, and Teller addresses + * @notice Initializes the adapter with delegation manager, BoringVault, Teller, and deposit token addresses * @param _owner Address of the contract owner * @param _delegationManager Address of the delegation manager contract * @param _boringVault Address of the BoringVault (token approval target) * @param _teller Address of the Teller contract (deposit entry point) + * @param _depositToken Address of the ERC20 token used for all deposits and withdrawals */ - constructor(address _owner, address _delegationManager, address _boringVault, address _teller) Ownable(_owner) { - if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0)) { + constructor( + address _owner, + address _delegationManager, + address _boringVault, + address _teller, + address _depositToken + ) + Ownable(_owner) + { + if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0) || _depositToken == address(0)) { revert InvalidZeroAddress(); } delegationManager = IDelegationManager(_delegationManager); boringVault = _boringVault; teller = IVedaTeller(_teller); + depositToken = IERC20(_depositToken); } ////////////////////////////// External Methods ////////////////////////////// /** * @notice Deposits tokens into a Veda BoringVault using delegation-based token transfer - * @dev Redeems the delegation to transfer tokens to this adapter, then calls deposit + * @dev Redeems the delegation to transfer `depositToken` from the user to this adapter, then calls deposit * on the Teller which mints vault shares directly to the original token owner. * Requires at least 2 delegations forming a chain from user to operator to this adapter. - * The deposit token and amount are parsed from the first caveat of the leaf delegation + * The deposit amount is parsed from the first caveat of the leaf delegation * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer * format: abi.encodePacked(address token, uint256 amount). * @param _delegations Array of Delegation objects, sorted leaf to root @@ -205,7 +223,7 @@ contract VedaAdapter is Ownable2Step { /** * @notice Deposits tokens using multiple delegation streams, executed sequentially - * @dev Each element is executed one after the other. Token and amount for each stream are parsed + * @dev Each element is executed one after the other. The amount for each stream is parsed * from the first caveat of each stream's leaf delegation. * @param _depositStreams Array of deposit parameters * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an @@ -228,29 +246,28 @@ contract VedaAdapter is Ownable2Step { } /** - * @notice Withdraws underlying tokens from a Veda BoringVault using delegation-based share transfer + * @notice Withdraws `depositToken` from a Veda BoringVault using delegation-based share transfer * @dev Redeems the delegation to transfer vault shares to this adapter, then calls withdraw - * on the Teller which burns shares and sends underlying assets directly to the original share owner. + * on the Teller which burns shares and sends `depositToken` assets directly to the original share owner. * Requires at least 2 delegations forming a chain from user to operator to this adapter. * The share amount is parsed from the first caveat of the leaf delegation * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer * format: abi.encodePacked(address boringVault, uint256 shareAmount). * @param _delegations Array of Delegation objects, sorted leaf to root - * @param _token Address of the underlying token to receive from the vault * @param _minimumAssets Minimum underlying assets the caller expects to receive, used as a * sanity-check bound. The Veda vault conversion is always at fair value; rate drift from * yield streaming is negligible. A tolerance of 0.1-0.5% is recommended. If this check * causes a revert, no funds are lost — retry with a fresh quote. * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an - * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly `_shareAmount`, - * to prevent over-spending or replay. + * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended + * share amount, to prevent over-spending or replay. */ - function withdrawByDelegation(Delegation[] calldata _delegations, address _token, uint256 _minimumAssets) external { - _executeWithdrawByDelegation(_delegations, _token, _minimumAssets); + function withdrawByDelegation(Delegation[] calldata _delegations, uint256 _minimumAssets) external { + _executeWithdrawByDelegation(_delegations, _minimumAssets); } /** - * @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially + * @notice Withdraws `depositToken` using multiple delegation streams, executed sequentially * @dev Each element is executed one after the other. The share amount for each stream is parsed * from the first caveat of each stream's leaf delegation. * @param _withdrawStreams Array of withdraw parameters @@ -264,7 +281,7 @@ contract VedaAdapter is Ownable2Step { for (uint256 i = 0; i < streamsLength_;) { WithdrawParams calldata params_ = _withdrawStreams[i]; - _executeWithdrawByDelegation(params_.delegations, params_.token, params_.minimumAssets); + _executeWithdrawByDelegation(params_.delegations, params_.minimumAssets); unchecked { ++i; } @@ -307,22 +324,22 @@ contract VedaAdapter is Ownable2Step { } /** - * @notice Parses ERC20TransferAmountEnforcer terms from calldata bytes + * @notice Parses the transfer amount from ERC20TransferAmountEnforcer terms * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes. + * The token address (bytes 0–19) is validated by the enforcer itself and is not read here. + * Only the amount (bytes 20–51) is returned. * @param _terms The raw terms bytes from a caveat - * @return token_ The token address encoded in the first 20 bytes * @return amount_ The uint256 amount encoded in bytes 20-51 */ - function _parseERC20TransferTerms(bytes calldata _terms) private pure returns (address token_, uint256 amount_) { + function _parseERC20TransferTerms(bytes calldata _terms) private pure returns (uint256 amount_) { if (_terms.length < 52) revert InvalidTermsLength(); - token_ = address(bytes20(_terms[0:20])); amount_ = uint256(bytes32(_terms[20:52])); } /** * @notice Internal implementation of deposit by delegation - * @dev Parses the deposit token and amount from the first caveat of the leaf delegation - * via `_parseERC20TransferTerms`. + * @dev Parses the deposit amount from the first caveat of the leaf delegation + * via `_parseERC20TransferTerms`. Uses `depositToken` as the transfer token. * @param _delegations Delegation chain, sorted leaf to root * @param _minimumMint Minimum vault shares expected (sanity-check bound) */ @@ -330,7 +347,7 @@ contract VedaAdapter is Ownable2Step { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); - (address token_, uint256 amount_) = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); + uint256 amount_ = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); address rootDelegator_ = _delegations[length_ - 1].delegator; // Redeem delegation: transfer tokens from user to this adapter @@ -341,33 +358,30 @@ contract VedaAdapter is Ownable2Step { encodedModes_[0] = ModeLib.encodeSimpleSingle(); bytes[] memory executionCallDatas_ = new bytes[](1); - executionCallDatas_[0] = ExecutionLib.encodeSingle(token_, 0, abi.encodeCall(IERC20.transfer, (address(this), amount_))); + executionCallDatas_[0] = + ExecutionLib.encodeSingle(address(depositToken), 0, abi.encodeCall(IERC20.transfer, (address(this), amount_))); delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); // Approve BoringVault to pull tokens, then deposit via Teller - _ensureAllowance(IERC20(token_), boringVault, amount_); - uint256 shares_ = teller.deposit(token_, amount_, _minimumMint, rootDelegator_, address(0)); + _ensureAllowance(depositToken, boringVault, amount_); + uint256 shares_ = teller.deposit(address(depositToken), amount_, _minimumMint, rootDelegator_, address(0)); - emit DepositExecuted(rootDelegator_, msg.sender, token_, amount_, shares_); + emit DepositExecuted(rootDelegator_, msg.sender, address(depositToken), amount_, shares_); } /** * @notice Internal implementation of withdraw by delegation * @dev Parses the share amount from the first caveat of the leaf delegation - * via `_parseERC20TransferTerms`. The caveat encodes the vault share token and amount; - * `_token` is the desired underlying output asset (e.g. USDC), which differs from the - * share token in the caveat. + * via `_parseERC20TransferTerms`. Redeems vault shares and sends `depositToken` to the root delegator. * @param _delegations Delegation chain, sorted leaf to root - * @param _token Underlying output token to receive from the vault (differs from the share token in the caveat) * @param _minimumAssets Minimum underlying assets expected (sanity-check bound) */ - function _executeWithdrawByDelegation(Delegation[] calldata _delegations, address _token, uint256 _minimumAssets) internal { + function _executeWithdrawByDelegation(Delegation[] calldata _delegations, uint256 _minimumAssets) internal { uint256 length_ = _delegations.length; if (length_ < 2) revert InvalidDelegationsLength(); - if (_token == address(0)) revert InvalidZeroAddress(); - (, uint256 shareAmount_) = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); + uint256 shareAmount_ = _parseERC20TransferTerms(_delegations[0].caveats[0].terms); address rootDelegator_ = _delegations[length_ - 1].delegator; // Redeem delegation: transfer vault shares from user to this adapter @@ -383,9 +397,9 @@ contract VedaAdapter is Ownable2Step { delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); - // Withdraw from Teller: burns shares from this adapter, sends underlying to root delegator - uint256 assetsOut_ = teller.withdraw(_token, shareAmount_, _minimumAssets, rootDelegator_); + // Withdraw from Teller: burns shares from this adapter, sends depositToken to root delegator + uint256 assetsOut_ = teller.withdraw(address(depositToken), shareAmount_, _minimumAssets, rootDelegator_); - emit WithdrawExecuted(rootDelegator_, msg.sender, _token, shareAmount_, assetsOut_); + emit WithdrawExecuted(rootDelegator_, msg.sender, address(depositToken), shareAmount_, assetsOut_); } } diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index d502d602..7930e744 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -96,7 +96,7 @@ contract VedaLendingTest is BaseTest { redeemerEnforcer = new RedeemerEnforcer(); limitedCallsEnforcer = new LimitedCallsEnforcer(); logicalOrWrapperEnforcer = new LogicalOrWrapperEnforcer(delegationManager); - vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + vedaAdapter = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); vm.label(address(allowedTargetsEnforcer), "AllowedTargetsEnforcer"); vm.label(address(allowedMethodsEnforcer), "AllowedMethodsEnforcer"); @@ -218,7 +218,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); uint256 aliceSharesAfter_ = BORING_VAULT.balanceOf(address(users.alice.deleGator)); assertEq(aliceSharesAfter_, 0, "All shares should be burned"); @@ -236,34 +236,42 @@ contract VedaLendingTest is BaseTest { /// @notice Constructor must revert when delegationManager is zero address function test_constructor_revertsOnZeroDelegationManager() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER)); + new VedaAdapter(owner, address(0), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); } /// @notice Constructor must revert when boringVault is zero address function test_constructor_revertsOnZeroBoringVault() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER)); + new VedaAdapter(owner, address(delegationManager), address(0), address(VEDA_TELLER), address(USDC)); } /// @notice Constructor must revert when teller is zero address function test_constructor_revertsOnZeroTeller() public { vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0)); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(0), address(USDC)); + } + + /// @notice Constructor must revert when depositToken is zero address + function test_constructor_revertsOnZeroDepositToken() public { + vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(0)); } /// @notice Constructor must revert when owner is zero address (OZ Ownable) function test_constructor_revertsOnZeroOwner() public { vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, address(0))); - new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + new VedaAdapter(address(0), address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); } /// @notice Constructor must store immutable state correctly with valid inputs function test_constructor_successWithValidAddresses() public { - VedaAdapter newAdapter_ = new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER)); + VedaAdapter newAdapter_ = + new VedaAdapter(owner, address(delegationManager), address(BORING_VAULT), address(VEDA_TELLER), address(USDC)); assertEq(address(newAdapter_.delegationManager()), address(delegationManager)); assertEq(newAdapter_.boringVault(), address(BORING_VAULT)); assertEq(address(newAdapter_.teller()), address(VEDA_TELLER)); + assertEq(address(newAdapter_.depositToken()), address(USDC)); assertEq(newAdapter_.owner(), owner); } @@ -306,7 +314,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); } /// @notice withdrawByDelegation must revert with only 1 delegation (requires >= 2 for redelegation pattern) @@ -320,26 +328,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidDelegationsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); - } - - /// @notice withdrawByDelegation must revert when underlying token address is zero - function test_withdrawByDelegation_revertsOnZeroTokenAddress() public { - _setupLendingState(); - - Delegation memory delegation_ = _createTransferDelegation( - address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max - ); - Delegation memory redelegation_ = - _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT); - - Delegation[] memory delegations_ = new Delegation[](2); - delegations_[0] = redelegation_; - delegations_[1] = delegation_; - - vm.expectRevert(VedaAdapter.InvalidZeroAddress.selector); - vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(0), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); } // ================================================================================== @@ -392,7 +381,7 @@ contract VedaLendingTest is BaseTest { ); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); } // ================================================================================== @@ -576,7 +565,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); } @@ -713,7 +702,7 @@ contract VedaLendingTest is BaseTest { vm.expectRevert(VedaAdapter.InvalidTermsLength.selector); vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); } // ================================================================================== @@ -780,7 +769,7 @@ contract VedaLendingTest is BaseTest { vm.prank(address(users.bob.deleGator)); vm.expectRevert(); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), type(uint256).max); + vedaAdapter.withdrawByDelegation(delegations_, type(uint256).max); } // ================================================================================== @@ -853,7 +842,7 @@ contract VedaLendingTest is BaseTest { delegations_[1] = delegation_; vm.prank(address(users.bob.deleGator)); - vedaAdapter.withdrawByDelegation(delegations_, address(USDC), 0); + vedaAdapter.withdrawByDelegation(delegations_, 0); assertEq(BORING_VAULT.balanceOf(address(users.carol.deleGator)), 0, "All shares should be burned"); uint256 carolUSDCAfter_ = USDC.balanceOf(address(users.carol.deleGator)); @@ -898,7 +887,7 @@ contract VedaLendingTest is BaseTest { wdDelegations_[0] = rewd_; wdDelegations_[1] = wd_; - return VedaAdapter.WithdrawParams({ delegations: wdDelegations_, token: address(USDC), minimumAssets: 0 }); + return VedaAdapter.WithdrawParams({ delegations: wdDelegations_, minimumAssets: 0 }); } /// @notice Creates a transfer delegation with ERC20TransferAmountEnforcer and RedeemerEnforcer From 077acc4c2f4a13c4bf4321fb5a0e922c168bb10e Mon Sep 17 00:00:00 2001 From: MoMannn Date: Tue, 21 Apr 2026 09:19:45 +0200 Subject: [PATCH 21/23] add veda adapter audit --- audits/cyfrin/cyfrin-4-26.pdf | Bin 0 -> 224801 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audits/cyfrin/cyfrin-4-26.pdf diff --git a/audits/cyfrin/cyfrin-4-26.pdf b/audits/cyfrin/cyfrin-4-26.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e1bbf512625a88d88524667ddb0bdf04e2d315e2 GIT binary patch literal 224801 zcmagHdt6l2`ahmm1Qha$%FPSpEnyS{(Ge0gok%III4M_62oc8|5Jr(v5vfc`QW+T? zA|a1@lw+vGT_7cO#v#lIP{_zVm?GjfaAIJ-?`Q4N)93a3<9A-?b*P!yYd!0^yr1jZ z$Mv1XZ_b?M=56I#`}37wt-R+>5KWM--C!jaTe-izep6K3#}hnfdrlDkW99y4^v1aL zTPC=_xiKPc{o?iOq#v!f^7XZn#cf$15o?v$n3vHa{aG}$rllw7`T4{Djyfq>?dMeS z?XcI*zuSKQLYjZUgCB}Z{<6vG?fzb}%W252Hx6%@a^QG+g-xa7tH&arS={)0O89Sk z&u zt?q}vAKLE@J)wW2Eq9lD=cBKOeADIB9Fyv7@CYk7qSX2qWS{PU-}Uhc~yK@o+=CmnMB zT-7Dse%o!^sRe%y+Pa~*ugQ1B>3)%;PJZz2e*WW6bJm`GzTAI@_udfiy}l7iQxE+% zZN#hJ#cOVj{^6Sq>)K>DT+JwD&eT_2oJ=ymfo@sMe8H z@jw0Bw>7z7$Ffm_qpyFy(*1dS?vJ5UCOIuQd8aVvp<={)@jvhDHvRNNepOE5fm-#$ zxK7n$H;0Q|>Dvy^8Nb78>F#607PWOPy*)X@sq@yj!~V5Jk~e4n`s1-njoPdh-R!yo z>4DCg{}kK~>KbkBd2^`M`b{61MTUPOaeL4Hhuq(jZ(cva{a=A=H>_V5XXU;ser+84 zL=qbjwcg4-DEcF4#VijG4=eX&>!YJSjx+y@e{`Jeo%LH5Nn6TTSBi3QX#p}05uUo%zsXx|S9wCdFFxL~iu6(Iz9-ec}s`me{XpioGmK7TmuY9-c ztz*mny=cfmaqy5&CSAU?c=Aut=!W$%i_H1$xp5kOS~oS$)+gMCTa1-mBr|gC zHLVS$(=@v5ma@4D{`coinOOL}&zfg6I%orGJ1{Umwp{r?P= zAbUiOSj30ln?zxMy$uyX= zuDYX?4_1aPNdCrElCkGrth=`IPRv$O`YU{4MR${RO<*y8E^^T9Kbj&BR4TU4SB9PU zxfGD$^4SlS4w}k4dGfcF>q_t&uZ-s{rp_yd62IL=z0#{1e0wwmG&D(!ZNdpGWb3%? z)y8Lr$L*ccFI^-fJ_{q3g%;R*SnzWqlJ7mQe=Eb~)Ttl7s=TB0QKtAPgD&|LV5|G> z6@9(Vwt5+FOP_AnZqkUyYIb4uX>+eSJBbQSXU}V~UsL6=2ApDdxP!(%{b&lv2rWqQ z`7c&zPyYnJ8m0-!!t?3k3ZHp>7r}>ZyE{#Zn=hMp+paBl)7a`g($f_OTqP^Fr+0h= zKFdV;M0mKbC3jL6HULNcGv$*^E_{>5ftHhtl`Rd%KRTu0)9E(sqIA@>D+b_+rb=VG zTy!!Xa<^#>-(*tNKXM}~Q_3T=6~>@lE|Rx?|3uW$o8?HndRS@P9=_rsjx_vgyTT(9 zqQoP!_)xaOg`G8~dww5GC<Pq0hUgyWqw`yrdu9Z`_Kw5(X zP849xe^sLxBFQ+UX@$_bD#m$$HQte-*4)3{BrAP0QbgE;SUO4SYY?JY2+EVY zie~Xw!VmnJ;j*Ojdy)*clZn!8WL_5hes=P9$39K>;6o~)%GXS*>u?y;%l(>tz`_GORf%qQI|@w9+F zT^7Hhad$TTG*xo??JYPovoNr+T~T0~Db|3B_l`0$1eRFHGC9kLn-4N_=ea;Cd8EQ- z61smiU3zBttGd;1zN_TgGN%DAI2$;Sl+K3hbkiz!7@q6imH0nBCnCY)L+BBaYuit@ zBGbhMtih^%-t70_ThfjCN5|YGZ+*Lku?sRM@v6M{`kV|HkwI)1R0V{u!**A8fypZ) zGfWR2?_^3qV$5&AV<*y&o+x%1?dqc}VCE?NZ{beEnwELKeP8`6qw76`$#83((qu_0 z%#?_!fqPfhj!#J#T@wwx04ss7OiV4~v1KZeQn!^TCt`xhDz|GN*OmU=S7;C|jhCzr zx<5xS8M0s?RV2h!nY2g=dS<@4MP$34>6&1fHc*zP%g1iHMi*Zf1PmtJ0hdqiWJt8C z(&4si^wetvVIj1Z$sW4SSSCu31Pd462T!{y8cauq3VK%-3B|U9Vq4NfMfH!uN&MZr znTqpIM2e(7t)edA&%7<7upuubCP|+>nC3^A^>K_O| z3R1YuOCLAwAijf7gdoZO%y^SOjMwXf(grMF>3NtjMNp{jth+coF=%zAu`T@2g_8e| z9BsJc0E4>6(_j3S8QTla|FzTbj_O45g;^v=QbalsX}=(BJXk*6Y@d}70kIl{b!Q#F zmFI{9zWgCO;2kBYtUfM!>0xlgEIX*oO@@J3>i~#=|G_GZh>-{fz)*;+Y129=%-I~m z5N*s>JY&O+qvse9%C|vd{tY+F94hIQz)hmq)|U5ncK&@;)ZzTWETH+PXJF=Rd&!WN zZZqKYVVLUxGk7tKxW)vTBDYlfo%cC;#_}4eI)N*1CNfhnHvTz4;&2cF16PL=)1d`l z2LrR_j-6Wz77@td!zmO!nH8xlGQZpuYoqZAX@H(ET2jcRb_~L=zaSqNl;(4C#_@mB z#e_s_fzkP~vK2v_iIrwVqK)$eRPYrkHYdwvbKe{O-s3qXy;8zxrZkLoWNg2T;R0=s zb-iye_1lDcd$VBUA8r{{(%e z2Yj@b?iJYz_jBZw7)J%wDP^`=S=yZEb28`y!#SvnpruUc1z?`AJmQVbqKXC1;`zTm zoCbe~Pnle2RFS3$CIInTc-8R#5t9-M;x|}%b?3KVyDaD))=Y|3wicQ8MfYRd3sxWH zX!7N%%b|`;rJFaJnqF15%4e(TfZ@$?(A<3;3f}hEFF6aqUVH}NYMUh!*9gw29ZIlc zfU3tkpx=ZJVlZ3VqtZdwNjS-8-32oj6=qqGrh;LH1NjY|4!X+HyWlD$X1lXU@o{d! z9zmff446@kXJ2Uk8Y~g`*?YG-!kvC##^F1FOFW>*LL}@DHkwq5*KrYO7a+*9E0ODJtZrG{91-`$z`swH^}(N>Q3(QQG5Tvv<>R|OF@zI2FdXT z0_7CL5LJqgx|A55HDYG=yCb6RrWuw^Bob0S=_@jE$7MPP z#elYZin2eHj9-r+5-u4V(GeLwBypYlrb~DjEB!0jn!tQvp=qY8rZ@4ex^E++1i1<< zBO22_k(?e(pxM)M{lL*yd8`^jNRglEl_rmNJ>~f;LoOJe*u9#WLQL1wbzJfQ*HgoE zu;=|py|-x(=np3%m}F6NL~sW53w+^|MK%1-Vung0_W`mBX~qxSMmpx4G#~Yp`=rS> zTtjz-Vc}OUFU%lOeO%92kq5-k2hdUi-X?}lBRkBKk5TrNWw?Y;hy&^~xx{9b9vWz` z%!g*#9&)k7-sE>kr6{(L+R7@HW#o2>OamGIIOs}vf}19jmo1m9aFHBkPXmu$phSV;}{6 z4w}iw!=6W<1;@w}*+o2#T8wX)cGT=KXypTRH=YPobPeQ^yJjZg`2xzgFZnwcjMONpdT7$yJHpQv|fkO|tfdx!mZL z5+OF+dvrt)rrr*G3wT7xKo;>OahNUMU>Y-00Gc^zMSe3RE2pT44EQGb*Dnn4ZgvK7 zogltDYr%M;q%2a}U#9TQAPr|AKAFG_08)J?Y`@E?SsIqK4d83n(cs75Bvmqj-Z=K5Y`pNAHZ zIR6UA?;p?E4&&)_l{B^pM;hl*BRg9O=zB$$UFITjFG2_m2y1=?Ms-LZ?zG*c3Pg@d zwuwCSTFh3lREtb-Tl8b=Oq`QpQ=WVcoP#?AF~*iF-yq2t+~lBH6H19f;VvI_!abNx zW%%QDM_i(ZGV}+&COK)o`qe?VkB6C5-tm52vhri*S@$6?t7a700`*SLkkq*VEg|+u z)0)ZHj^wtZl2epaKS+Kt|+8ue&365<6CSw-xU&q5Ei|FGZuVV z_+!-}VlT)00~mot5I7FqA_4JmJ2Na#ax|NWO}k=7J;I&eCzZ%463dzpEs_t5nr6 z3;|y}!2P{?qS0Krm|O+#^aMtyMpE8Sd9 z5k_kG^NQfl;nLD!;xDZ%v>@$*p(d?0Te$_?EAXn3fl%;{(sc`dClG#%*mR;-Y6k@~ z3jjN%TS5!3z?_2E$1@|$<_4o2bXC(?I#pMh^s1&aI3rgsfCftG^u$R7* z=oSP41abOv&U|&|Z}@z#L7$wsJ!#qhq@k31;`}Qhy+oDi$(7EDk_^vZ8D^6omV zKgztaZDe9nSXF6cg~9X=Vnh^o(gAQ=@Zjr+fLtU4Ad?3X$v-l!P+P+jr0c_z8@NO_ zNH{ai@Nte1NDhFMXTW^(!ADogJSmK4L1gk%(>~a1S!B96@_^n|4sO1|Rt;|+$knGp zpOA1l4aK56_2Iq_!lL!bT3;9$pwx-O0qcK*@YXTbd$mAy$}Kd3)?_QwpeAACp^Nyj z?=X6naH=7~_ZQB6kDwja&lzRSF0@>o6{05ie9YR1eXZ_nS*|CRqcNlhROv95R|ygd9@*j~fE=TUAm zbo-B*Pb2o$BwR3b`LiI_cOV#eSkJUkI0tx3IU7ksosQ`7UXkE0t66Z6V)z$0Fx$Kl zO2K83fs=^?`3+N=fZwII#DPPv5(|zZ^nr^>noV%Orqaljroa^NTT$4;wi1SfLD1+{ z`R!iml!!hXIw_Itt_43!K|}cvdUAenRt5k3Im)f)!J-Ed>K{3eR2HcwEhNuX2sbXc zPf^(7Ht0=gL4E`RKi-$vwDLWrqCdP>DqwpQ$l=M$l>XW0O+qI>T>gEn5Z>2=d5jjw z>3TSf&z7IBR9keCm_kLio12YgCX>mmVb3=>#>5n3P}f^Bpt zr@o{hWsksM8AKvgckeW;Q$fbjBULH#LCV65hD&0?{P~XspUvnadwY|ZdkNICZirptV1&%acr9xWjZ^t3W* zjhYpVoD+SY7OaA4CJz-jgPqII)&dP0lBQ~GYosd>%^0G|#G<_z5R;t_BFs%53_v)b zKN{Xwq$Ru0m3t*bhxX$(mY0J@*jzSJ7xy0^n87eQggDLPxJ33H^1EzsDZdI*5RM>k zg58z->R6hmy9jp!GvbF659`K)PZtAZ{$b0BAvqE1-|HN7U%`f#GOkoZT5HxTTMgf^ zfG0T1eXvZdyM}FFV;FZHF6db8NZ1R>E)|qEAo6GmCK({sIaV8mr5>UjVW4<_0~P^K zm!C(1e8;r4H)(+Gq@^Rp@P_I5dLGg(aulMlQJnPfUPEtZ(0>aUw~Y4;R-l@J5ry#C zAIZcmUqO}N|D3Chn~hZ;QN{qJ&FW9wThUfxMTumJVVtT}{()er-~I~GIOw_&Dl z2E3j8LwKk^&U0j^f#+u^CY-TGPE7&;nySCh!y&OCuiQl=jZL20xAKEgKpXiSrJ{7O zVA9||MLT>}?=($k(h?vm`B@8QVx#G4+SWKtW4U8=jy)C2odLEr{OO*SCrE~%bV+mZ zoCGw2-1jKJ1qj1ov&gbKVny4HAp%80DtnfU=?BDNNwA&#A4xBJS~$v|OS_Z+fe{oe z@#kxte;$>CB}+WBf&Vr92PAET>8LQ(TJ{bH&r3XQ2mFH&stdq|DLI-9?*~X3j{uNj zVGEi6lxJCpheBDn$p{bj=Ui8@6K+lEQ)KHQlm@j6Z$_4?o*{^u3U2G@E0H?|Tsr13 zSr+4l55bXhj$kq5brHZ!iOO(n!J&H5S?UHlt7VeY!^oCwxFswe-43C|3s2y)_6UTj zg#E2WU1lq~A1lPh?rVj|5<@eYPi2Y>BE2Kkf0rt@8y~r#z>)4-(LE9Vo4SC(Ok++| z8ZVZ#)=Y7QmrhTE!~W7`ngwBlkIIfYsmF7AMl1aa4La$8?5{sUz&XwfnsXiDBb|{c zs>m4{5_r{Y5l{ju4|ti2<%>cK{4V&Y>wiZ;K<*MbsJyFJIxDu1OGBv5nBoDBr$RC6 z1%)Lkg*kB4nj=F+_E z${{ReP7PPDXk#^J78h&qi=WIU890!Cp5Nf{6cz|}BeY9vbkL1Ntc`w|>2;!$a-62? zqI7p5$T)#8=6}&?do-~-v=jMTPKrDh=AYm19~Mt_y!VwBBqPKjVrv4e7#MhwOZ!lI zu=%$++y905bui+B>i>C3mW9Vui-!mi)Y4`^Qetx#?3dBQ&FMMf)e_XkvS(Qt0s?Ud z#gLRwlP4G!UT%jc2}w_Zm5I$(T%lgt8BBS9{UZb?)_jycrCspdrLUNYa;`Lzb^OX| zlze<_-y0wedqN`Albml;hoWb+N@~l$kI;nb zi=h-ix1#HfBSXfB@00Ro@mdHOYF9e_C5WE~he8XmldHzgZXWCHkv~f;&(a{1(~5}QAw7>3!P7uxLx!YS`ag!Tx098rb9rYy)TpRCajxhsfxrY7MM z>U{U*engZP5mg%5a)Vn3^MX-rH&Dp{*a-EeXbJID%OLIqn1$vIJRT%S5Qk8nnAua6 zG(;CfrDQ5lVBh9_`r|BQ^Hy}in1{P zQ)dD31l6MdMc4y2A;>TBgcLRs+o^IWl=?sWhkPhEy`4sB34JSH@iH3kk3+DCiy+QH zvko4svy=jYm5u})C7v(3Wy8i$t;(A@ebz%|r7>!96)pz&vmLlGEss1Dxd+yAF6{>t8>cIw_zo&W zs%-9>q#-mU@7!9HD`rY7l!u5H9^i#7TvRnb-&t_UOwGY4C&Wpi$MwM0-3+s@Tx3WC zW?_T^0_Zv?QnZ(4-*qtMynhfbxIY2G69NL_fq8HFT&$1K$>2(XBMJ3?J(q}$eFZ04 z{fbYcFp-Z0+ZE9xo+Y}e+FgTkx*b6};yGr9{MV!x_;1JGS&q6GR2L2JFokDkC}09? z>}~8w89sI5XU_wP-M>x_WAa^iVzY&VvMd;whN{+0Gf)q_1)as)QEG6|2 z)aHhurwFY_gZT|GmIAT~uT(q=t0pWWWuf3^rW`6ESTLCu=|?vjT>KzBFo#*$v@a2+ zpw7hr0}=IDWs9+hhBd_Qmmx1=AWJbU_@^Rwab)uI6R7G_MV*Dvz(Jws_QlbtG{cVY z<_$3XOhL472zW%Wnqqif|0uA)bk=}k&`G$4;0$1m1^+!bNPfeZCy#?`xHJ(OGnZiw z15mWsNZ-*y`r6-uj|E7sXJ$tjL_G(zBY?8Uw067YuY0HlZ6o*0{CO-al zxYH8&#CAH=6$b6*ULH85{&UWDO8PEQSO+Kcl3Nh{lr+O8Y%KCpwC6uzF`3Sl5mt4% z^eOegDB<3rL`P&D7ZIL3x32`LKdmsw5e&?vv^+dCLX#8)e<^|*e4w`5X;|S^BO}~k zlO>Q=0KYz>{CjPXU2(5;VnpAccRQUEk0vs)$+awCA|AL8<3~f5Vxd@0dywV{|8@6_ zEI(dbDbCe~?bu?haZ-DuxkPTd_&sC6zE|gt@0Vl ziX90_%SI6PnA=G5F{<>DINrjIn2N#%6xlPoBmkmd!A_I*6h!=iKFR_8UG^>W3k*ua zQ3#{t+m$vC9m`FLJ)mDXSw(iqf`^Tv{kcr&a_avf4r;}sg7D;i+`-snf5;z=SVmuK z(RW8KFDrC-;Cip6`&#SJGeZp z5>PjW=1qd2`d2mk%+U!Z;Sx|DWCtW7!6J3H^D0q3LdF{2&CArh5Ain*Ux7x}Sd#zw zYpKhN_#khR*=}H~m8G|0x_+Q!wnirMlLDO?ZfBx*f^8FuJ#{W|uu-^a<0KfBIdTwY zrJ%Sw|H01`c_C{PK&xRs{0O!vza%^qom5DZj76wh!_A>urS1>^uGBli=2pH&qD+x5 zU`|(D)_4R zW21Vaf;NgGzu})C)M^L4__iR$cMcP;MvFYhE)n4xMFiBOJN)XTJcN+O^-Ln{)v=wn#~Hizm?3t1@+eQ z-_bNeX@Vlojg(75Npo>dc(N89s#$5Zl}YIAASgk)W-at*$*=LHpiZ0mxGrhc|Gp(X z18*>3WW!R9HDC+H?XB{5L~C|Tk|OCwmW%hoAUY<-iCWgeEw)m?!vfb1Oa1}ukIrF> z?Z-n|bO8hon-Gb!S&a^_NMFYXz$gdJfTm-$L9fnJcG%J{8j(-a8PU-79shuo!pRAe zoT^J8FO47E6e)U23Tx6_7(&UTlwtlrUIJMZi<2y2pHAp2G#}AR6Sh%CDXeC}-=cgX ze=wgOQ+=#fk78MZkcn;97>Cf0fOiO${PxE&sF3?Zx%(4V;#Aqt&olFxW~=HK7E`<{ z^G}~CAk+S%VQ>pK;E4eu`lVFUx&=Pzejq=LXOJ?D+Y zRB{w2!kcTki3f3HB2aYsk16vA{@|W%#XkWU?if?}sB&*O8Gd zCz5dV7|Qp#%s_`rMA zU_!Q`QnfCs>to2%5IhS?c2!eISllv1Ws@%LQU1Bt@&A+6nt? z`RY#B%vOW6!FWvD8N9s_v>><$o&}#q{y0Ah=57g;8Vxjy!r%VYS@+`g5lWy8uK+gHp<3ULW-macOQ6@JX7z6254CY7qS3H4B zJ_EWt#iKQf`6H=PJ3{GA-ZI`FSOR6M4tM$x!QDj|E$rm`lCI%VeLkSS-(3+h*Oyg|6Qa*`!C=;LCn7?_HR7+)<6;$W8_J*D$Fb0339UUj zJXE0(k4FuFeg6T#90ozgVqGVMqu*FkI{~f;`dR?cbJG^?fozxf&r_usK31lo3fMdy zRl=kMXmyx6lYuk?n7RQZ$)H_^)m{~U%_bM;4po_^vu9Hz;bqVVz&W*TuQoK=|ptqa8=Q9!Ip`h+U}!jm_s`%!e^L@fdv z!&DZ2E&u(H)LLr zyC~B~Lv^tYT1EEihcFw2)WNHVkz+0O(pV6o?0oo1aTN@xG!h-5jc!C~*dVT>Ce`ph zoiu+-dD3>{fI~{wrMbpbNAlr8kU9(f4!3qQweXG(y+sE!mDZ^j5Vo?{gO4P5$4v6p^__`{gPy@-++m zq+bgqTydBc_8RAr>1oMgCDu1Ee+6fTMSTu7+j3d}82J{px85MlYBGAXR0?xfF6&EGQaCij2eNHv|A8Z%|S&15SbIPR4{C6pON~$B3bT(lJ=J0)WiqE7?=- z33DPhiH;;r9t65<5&ahGah$6fR~E*Rfem+uQyPc}bs*O8)@={YQdBF8SZGAgu6*HB z&}87xu@<=N!oR;2b$H@gI0eI6e0~f8Q7jVFQfDL_I@0^kr#&0d(=wepmQ*Gb+v^mq zb9&wer|j9pNXCS_eT5^KJD!1F0e_kWP9u>`adS#sgb)EU59q6-*9FPMBbBn@GKQ4t zm4J6RZ$?Q2FQQ&#gey!^uijo<-2O*Z%Uq`}F z|1n4jkV(EQpQ`C~Ufy(>#6gpXdCWv71EQ4ylj2zM6-<-Ect@MRp=>sTMQ5xr5#D#o z{jQT=D=QkGI|(~MR<_Y?+N*U0NC@$(j72yJi-X>$F%U`JTkMP!Ii#bls=Ll_5VhQ7 zVJuK5cdLJXe|{~wEerB@a$gp`orY*Lb1%OC5SbfGKbm3jdRD^zywhk8sYV9~-X4`V zQH4+bZ;&@+kC`xfu$k6_T_Z={uB_ONLJup}5MQ6Y2S3W@htOScM#|e+`iEd+-jQyS zpo7f=I6p?rC}z_#sxc9#U8ntRx_X&Bgu&Mf*N>hp4fhZ-Cv0e`q8wruD3`o7m`NQiO@>#)V*7q+ zZCzAFM+(W<(2!I}6-VGYI+vIff#x`%7?DyW%%$AqB3@*WG!Uu*5wI&t(Acyk<)s*0 z?7;+jk;|1!Lb>9BnZ7kGb_R<>Br}~+ALPXZcBvwQuilz@) z*S;^$P?(->YYFT65gj~gh788wh3kt_5E=A6q;ise0zk=Gl3MRXDGw~FKh;sO81*1_ zY$sTt{1vVoL{iOyH2pUeTwCS$80cTs=#iwjM%%J|!kaP9bwE!oUZKr1%{=MKKZeOZ z7E94MuK~3*JV3sx#4{Z&la3p_(+ihLjdqG%mQsp9noofOCYUILMQlr~LyJflz(E>W zeG&DV3}*8Vn(B(k8vcM-TA*~)t%q}F%|5!hke7mE{3aJBV%bo{B+?2xWi{VS07F`W zgKvdmGdMstNyCeN+mqWDZMe?LyN#H}Y<`Mr3#kc;G?-kOPX)%G^qJnoPY+N_5$i18 zQeWd;lfTIk$g*`f=%phpM|&cmZm7B=l4(pJT8LgX{3K#I8i=LCAufz$1ZRBvlm?2a zP}vlRdsyx?6SiIJixi#y4x1{v}Ps_JZAbSjn(d1>3%P=ZmLh-`OOMxRJ z;igpouW>31&>|N6et&57hQAAyxqeqp~2E23SWsQ`h+dRzL<_=0QD% zJFHJyeo-56*{HSMln!k>f>9o#4SLB!Y8rp7q$96J@$#=DTZ(&i)tQ{)oSP%b|e*t zh8<)mLkDn0hDiaI+2*GRHUM57xhH^vIXudadTHR1f{9T9dpVLnY{5o(FASwDfd7w| z1wRn_(nwCY1_2gZi^6^u!!7?^*a{e#!JeQHks?3g3pd8@l$XTL`I4c8n=rolEq10# zo7KJs4}cSKu`@VR7)7c31K>utC)6=)Y%_ir>;1m>DI4wUl^%v}o?<_J*q;i^MJ}U> z6u@4oFC6?d=HMsN_NUSHp`c(j7<-E%Z*>ep8d5iJ7HqBK_JjH&L`U^Q1n$12VYc7# zF(1@?h5v(vA257+1a2DqF3|TC4CFIVgRLn35FAfDhry&9r%6#7Q}k!MVQX(F-C=?0 zV6%?FQu{UjI#JVjOg{$w|KtaAdak+O!lgm*_K+A=>AtfIm^YwZMi62AC!*{dp!*xZ z#aHlw2ujrA4v?9ddltqS?mLLPh3SLs8-W|!)!l^tBnvmmEPQ)a<32Oexo^jA8eEXC zQzuY+1|q}5WlYC)WuDG87`UsoBBBP>JLJQn2`GqJ>y(dzP(=~M>$uK*L z=qZ3X8}3H6aRlNy>e-FdDYT^UOa<8NQsy`h=jqS-?`x>|8RhS0Oe*y~F{k!k;CR!eD8yiqb%GDu;53B6=Y-fc1ne3#eBbF zpXB%%fY3mK6odt5M2c1#w~D5e6^<}Q8HQ@99=r%bWjZ?*6(Ub^ieV`R`>+alYRPzv zDB%)$Xh9fsJqldOZ1quv?KYgG^BKRu%@@=hGjk8JXtsNg;UJQ;fd2eQIDzQ^gNI~k zs5qj9;3io|VsPrur3Gi5MCpVE#A#@!5V#XYH_G%E0iqDP!MM2l zyF8E_23jp{l6sgq<{v!!3!Jlg(?XMx{&Ih znraKK2&bTl9B9V=a9>o~-6WX^qmZP5lz7C;cjZEi$-)LLs(2~#OTSPXdmnUh4PT0h zZJ~-F$-)4lbOZl`C~PT;J>;gO*ZINBZ7|E9$y}IPEV7-Cgz~Pu?`$~>z(^y1J_y8# zFY^pT(p5fO@ZbzI$wz$v(8tO>jxW5cEk6g}$AgiWj;rC5P+>wUvjUzFh>oz z%tKHNu#T%IL!mL-fVwIOWhN%}lVa2r4`~KYC?yczi(F=+7>0XP?N##ohE7*0nqf6E z0P7BsrsHNlJHmjQH2~##bk$ z7#M)DwL+Tex=0d^SCP6H-Y4o80F(3$rZ?Nx3~|&1foFCUVbIVf;VoNa7fk)+SPWy* zobN+8B_73wt{kXIGxTXV1q9pR{&hAo|7_Q1k3&}t?Mae)9O8osA6-Wf%J%-pnHaOETa{tm#t9wb;2c)yNRf&8&AvKKeWNgQC! zx4qIm<%pw`K7rAC31K?bRm|Z!+-oMpuETtJK1RC&X%bKG;JngsIjNi&?1UI>)nmov zXNSKt#lDZ_-SDWfbfd8uW9 z+gCN&V97`p?ERJKggbq1l24$;$FwJ~O(tfkqS!$Fxou*a63@*2l<+v+HBJsbThpPBd|(wOj@9NQiRVCphLvr-W$q);so>qxzXBIvD~ z55uk?vfs~6iLa3ZvAjOT@P-P{oQ30#f*%~pPc(1e+gB>bunk~_YJ3DU!e(asJ2f~cc7A0U5R+z;^x+YDfwLN%_D4_nZ9H7w*G# znwL)T`<(3<4Z!7t4iM~qckLHw5p=#(I4o9^Wmb3`6gZdCSPC( zyPQG@OcOpCkucPU)kzDf6XT32yX1HeN_fW!8Z7AAb^beQj^=8kz^|Sv1o2b`p%?*v ztU++WuhCPuvo!75IqK^FW5S0fnV8#{4BX0#?R)$e|8!Xn^*+9WsHRh}k3!7LU!}5FL$HRhX<)g?Bk>zxY>o@DjM?}0saUWfrpWDnQkm^+>#CDtSU z3xp|p!xjUP!vxe#QwQxudBcE-h7xLTBX$A$TU^3TU{+RtQLRC+BXZur51{DnB#Yj4 zSP#pXQOV-Gp-$AIAM4=`?_loiSnvz=Gia(ras#JGxt#P98_f$~k7L&OC6kZn4VmoC zD5S`O9t$&0EIgvi4S5iMxEfPe!Zks1 z^iME`m-`6>HNlITx6$O)1qVTn{el>!s-T?!bqd!Jb{7k6ETM*qHa)BN;N&4$ffPjv z(GJoO+qDq*7f0>zVfP>MLe`RF2@VT|SOFWCIB1%>@OL?G6Plqd>k2P|aB1GseJcv+ zl;D%jffP^nHVT>p(OqP93p3&@hTq5QN8l3W@sP1xU3mAU8#@Cn3;T}Ekz)r~W{ej| z1}A5uu%N&#dTOo+&=q4TfNK^)H7IlfHXn~rutpZbdIv7caW?4&cBn#VS`fntwp7yEGTv$?R3S?qyr+q%ead7=WC3R*-z+3R&d58)>#An ze%}F-Hu&K(52PYQ_Cv(BfBQ`%f7?O#cIaB+li=ZUx|HQ6dB{poTS7XUxBfQ30LSFw)Cs)*z) zcx9^HzZkLE4m3=|zq{gg9!(_-RXXF7DNq6r{6#4B4Z5eULacA@qoiNNt$4)i0|c;7 z-XSEQ{6x}8;a0D&CL4t6u?pSJT1u^wBcSawR5Z|EVq*vAMGBMwve!eKugJV`l~*jS zhDVMf^<%H?SXybM90PYwO6TZ$@G({d0})e|MTS*#)8sT5+I&bq>_urD9hgkai}9C5 zx*GmV_&vO51C?Ar1SQopPB;wV7>WQEzXD|L!eN=wiS8KMdvt8%FM~c*P@JDRQcz2s z8rM^kX)MsNSEQ>XaDNemkfHYnQV!)azp56`)A2~&*?oS&eoh_6q>e`NXI&36&FHw!=Q|RZ21@9Fd5Yp6@+wq+R zE{r*Ss`m*iXqC?)eSSERSuA5&;cod5wLZ2m1N>)al9YgFm)sT)yI1nlsotCY zp*-{zZaXE{LhK(v<@umL#s>O)pjr3gt=jNKNZG{TX|JUqt$lbdzz{awPucC zT^=)1jItyA5&LQEIC%jmU(rSvZQ-EyxJlkcZq-25+z@p)5iegv0Fz?)ly7eDTtZ^@ zjzn!DL`CQ!PiGz+x4Ns1eb4_bG+%AFn9kCc7l(kNTZ+=+sEG%>~&eM|9gq0_~B&fkitr`eErfVPNVA;d?^vlLh!%^En;X?FY zQN<51M3R(|y?Uozv1SZso30C7xs;*EenJtbEVx%K{E4~jQ0F@5BI$0&Fu)!$$}mY} z`kLLLoJo^KUkbP3zaBr|M3>CAYw68hzI0U#?$q}t0BIj&IcTq;y7l4V3+W73Y&agwmx1~B zF>>29S2=RqWJExL{bkEC@iGlJZ4X}2;0)x|qjW(-{!_r|hftYCjmFy%HBqnAn*lJt zy33FRw`fUVPOoY?2H!9;Gzm>%oj%SW4-WNQZUFoI<0nutQmW4xRAuq81u{??6Ft+7gS_~GKj@x=qUs8phf;Q zm;hK$-{7t{-pv>Kr9NzGU)FIgH7B5$qOc#h{5QpZ3mjT11pg7m-w}6E!a;!~2w$Da z`R&+d9KGx>b``a<)I~=lSmecETDVp+F=PX7Wb5dik$8Dk=P?W*0tN8yy?BlBv{tGM zYj#o$7YzQGb`f8Sw{w1_*Ri`YmGa&*g*AFKq44J5?i%ORpPgtdD0rg0FZGv9PSIt2 zehi$YHGOK#h^W{bAQaaN_vSZD40&sa%&P%4VYPF(r=j=d`$K zUkHOh6dCI-4$d4u+v=F%=FpUO zvShG>mCjJB5!tstUQR9rL z*y&W^1rCg>^*(3=t<|4UweEXM$08bAWs9?7FLReSsb0xKaf)Jg!K&#t(W2rk7RN&g z$e4E-c6%`nuH!tQ2sBn8mm9 za<2~5Kj@t>9ax@T({1zM^md|)9VipE8{gAM#3rRS3b{`c=HuW+YM?bpMRB?0FV5f> zFW2@Nmewr29oQD`Yscws1`R2@gaOz4c?kFy67-6Mo%N{*_Bvg&;!Qcb63SuDq(|L0ca(#nd_0oR(O33 zWprCnnr-#TDKG@%PcFN=R(HRS-v5p8cqaE5@`CJ#>i;~GfBtaZ)y!3fm8=8 z%$TBE)A4fiXqmsjYUg6Y<74Yj>2P_U_y`4gI>L2n+Tb48IG`q2qaslVoVPqda^wvydugbwekhbIfLAZNS z{vSk7OTkze{xV<@?6y7`!9XmgLh&ug2tJQ_)x$OPELp-$?(>geYt*7f7a_0p)=CjL zIhfniJc7bH2hBy)pOPkN8iJ|vdYi6@J*3wPS{{p4z8TNJLaAo=_ z-8I=CEmYaD|A&L1*(lBQM-VoQD%<`+Uc|hxG@Om*QlextD{cfRXl>)2wOhqO)6{mzu45e8Tz*rdF~I$R-!%=U>tN^S z(2#^(zrSBU2Yap_{{T}bJFe&9eZ9CqNHg$x>j)RmjMwc0jTeux8S~|srMO_YSK9vp z)5I$vv@zV$;7>=nu`9gj+{R zwxne2ohpvVVeD0BM(O5P9jm=KrmnK|ly(4&DuIO(7P9tg=m8%0$Ga26cBnX?6Wr-O zq4ppszEUAshLTq-C=q!Pf~Q>ljoOz9uA{b zRhEkD1kk3}Kr{s3llL90e?+ek{PR;hjZ(i)9VFqS+((7C!wcK!Ht3#nq08`1jZ*Fm zJ%jDALVnO-K zL+un^zCVT(;35p*s%F4(zbM&xIf!w;ser4bM#`HNP@&Ou9Sm=Q7(tqZD-|?}_#t^l z6vC)s5#&ghy7(f@G}0B3r4W!?2&Pr(4Q=%sK_Z5+W$(X1=x}MECj0pOU%xHH8DNjW z=zMDE-K0evuKq<)gkS|Z??38p#rNDALD3Ncw^VCQ5H3ZPN%k8yB|S4ln;4ka3OPT{esVE24Z>UHx8|Db9^MOYB`faOOb^$Y!P1{ID-c z)nHKz<)JZTGv3hLoHI@kP=P7dPnqw6*$4W6$#BzN;o2dhM*lX%!PBS30S$pznM*LI z5AX}Ny3XtB6{v1CQmgP@(KedGPFkwc(al55Yafjb#Tx_9;V!+&KIhDG!tMlR;G5v< zRZS~iC6KD8%yxAE?vS+>Wq}27QRDDR6u}a#Ht=4g%<-^@J#MVBzctnHDl^W|0ubLk z7K_sWUZUHT#q*Sfdklke6WB{TGS@a5c^F@7(Fen^+a9+ii|}V5y)@LBDFnupL3|g0 z>z>>V90K7XSsgOrQp;LI%p*DcUoJeaTijL|Rxq-d?njE!;fw&qc7&Gbp4zEtnh95l zSb-7Td;p&Z4b@Qu!{8S-e;HF^GC9dPhLOLO!JZ69W--PoWyy9OUXomM-r@6Rjz=fI$N z;yGlF%V)u#OWB{qP)p7(c17EtQ^{3PbC3+h8c`U#aF$wogQj^HE%qKJQ~MpuEupuE z@BHcP4{v-Lq#Wx%w!h_-tYt%1?Ong*>4Y1yux?CSuM^U&siPsOMTp@5*0>b4WExT$+}x^+Q?#8w1}OZIV;D5 z^qn2|ZNu54i{4OM^D}(2Zpey9|NTYvqo)oZu!S3gl|3KZ=nIQ_q(Ny#UMB<9>7}M` zYwy)AD~#-WLrFxZ7KxUs)_C9*P*0D&mfCkbx(hEt+NAdL8C5JwFljwY=Ex#!0-gpd zok!spHgY!&CIaLKma*@!eU-zJ3q^DIMb*KoRVB5l2Fr+ZwZk9$LhI&_;2IsS|4}3g ztHq~V!9_N~8! zhsWUIZK-aW{1{p4fn~%kOZKdrMix;%TXWIp%F{bphTB+qp;8qIij2>|?vL^16wQ5~ z!&V_gx|QRB*W)6<$D-)1AGctQ_6;)mie2IaQ+t~zsBQDx5LLuEZK}_+n^@c)r}?U^ z_Uuu2YrK3SXvwye9%=>EyU?PFP5Lr`jwcuImghCd5N-#d@*f6?slwBsh| zQVrK}?x}Ik7$ajIOL6G(k4xt1_PQPuq4-V5qb9 z-eqtyc8ALE7WBNkWrY>k77xzr1u2BN&0C<{J4KoLsc{j>+f*~+ehg~+wgLKO$9=6; z?J4qFitkT8b$E-Zd?m@~LQy(rv6o0KF?GF{v#5-z&4N9BOws7IRCVjFAXo^ZIvyo_<$HzsLNTFo6b&z-{0}dwxi#>L1*9lH15_T zED|HD6;i!&Dm(v&MrdihQ?Mre34v2RmzTr5SEXO+CLGV)o!#Ap;v5phk>rZeFcpWXo8r z89BPRM+$@Xfl0aP?i~Ur$`|0!L!r8En!Abtv3Slat$R$d2+T>f>75*+46>FvYj>R$ zz9|vgah8q7uHKNGMI|J6sFX$QrmPTk^#ibPs+Vs-*QGb!rO)sH%SIZ^uRLHS{z>y{<8V8$Pya zywmF~w}}93W$r2RPg{%q&K|XfnDE4+e?gN|PBz^4IrK#wwg^_HczJ8xbQ1}S);M94 z@64KLrYZID2}3!w=1b4Iy9$|UIeu>k%a}w!5aI!CI#CO?3>yJVUZ}!#AHNGjVF&5N zn4w_Ihme3O5;}MR0N3bH1^cLZyI@A#GCKh;1)ePW7u0z%PChXfVgo}WHZhKEBNFj@xlIxAGu7rVOWCbQzFF1ZdnK5b zs}`N~FN_3JBXUgN;Av4B$K%4qO;D@4s#^xah6n z@2XUhnR@dUW0sI6+q}MkAUNkk810cidfU4Ui&8(pI;lSY{kQp~o3!Fp%9aI+Gqn@3 zjF-iCmkc9gxS%q{9kJrf=8quGL!>f`auw0Qv;j9jOko?2g0HYIID z6OGzLW&jvw|uf{snV0q4j*Zsw}QG0^dkv-DNQr|fgm-?j#PE>Zfy zu{t!4E*AMs&@7p!af2_(a%@b6dKD4GeH_&GxWcgolHI%HZyhkx$8#eHr~@XXg}yKe z2}HTPU{MKFKXfA8n&)(2Sqw0`dM7y_hsKL=03&gl)rbf%syxZ@?=ehVu$KC2RfPEamLX~(ph_^!TnHEV@8C9@f~aWOL7T(Y zaQULNt>AbJ@!&h~U_lUk=f52&R7eqdYn>cGzy>(YK!in(Z!Z4Z?ibCjLwI0D8ko5o z%q&QQd2=|+u02fO{(G_Zu42YWuCPOusdM&tHtpFaV$SM4hLfslplJrw;+>Q9o7-HA zvCFV-@6&^uv<^QuAU3#2S`q*Bj_3A!bgYSW&}+vg{A&gv2oI&o4I;Y-BIuyc1Vj{G zX+!5`6U7n$V2A+_x02~tWZR+2p8;0O$&c9L9^ zNxAo+wD-^Ft(y4{puEd52cp_Q^8)g!pMW_OVw?=8Hu-rr&(>+}%O04OR0CW%%C-7ekol-i{~m6SwU)?ABejJMDG(rHS1armcxY$0X1tT z*x^GVP;1_>Cr8f-v>?~|All;Z`b-3T0k?*5mcL;STxCExU53_zY1e`gJN^)S4OdWU zfdi9l6ryV)ZbV&s9Jhrh^x4=v!504{>iV+LG0+pWd$Nn}o zfB-7T;V)I4hVaJ=h6Tn{Faj}GA^4miTX1;~%t7&qt=AZzOru_hUj~KbvsD_#_ zfk&grL42g+)J{Nzzza}PH+UcJ5jlh`sPagA#vraO)f%M{3#2uplDCItLVu#O$sI`# z>@9e$3>HYVmcha)QCJ8R2F9Q=A(dn-5m}fuJ3}CvXd6R%n_5w^rCbD#WltfE+^vP2 zg=8y)3aGaOX&lxXO9C6Mna~6vJ^3JS84@lPC0z%%>jG8zaDcv1tqcpf{@Oqu@;g5& zmM649A~Fz2z7{L}iGCq`)BuXKj{y#l&2Je_fbIcD!3B^It;eX4FG(H(R_6uy2gdjZ zuYoxr6WsuG6N4?Nc5Znwj=8ktlHtsfgqxDz9|X0ZbOPP1+4q1_*rBkohxQPzK`pbH zh{@XyX<`Z`WT*)t;T%|qU)i|U(n9Xz23<#)0+?PNEYIe(GqZ?vjk*N}#0S>=&14Hy zu6X@!+ym7TBvM6;5hyC!ry1X`@vkJaF(Pwibmt-fU<7gxfXM8B1vJ$JXXk2ap#=4z zFbU7%>ak4kbu~W3$>$4{9{pQIj?ilnm%WBsW5d4Q4@?P|MVRzar}!r(jB+82@$W^f zuyF8#K+ry|j#RlH)rGM5BXEnri$I1TAa4qUMctc0Df{7BRJzJLq=_Nn3ZV!@1UzgI zh4=n;&9EzC86{g=N@~3c)bOa19tM$(Vocb;PNrt44D;&P_%pf?Lj0NGFW|#Vuv)Jb zf}uQCj^PrHOrl1>y;9sK5n!X4 zpSvGZnx`=TQS`JW>O?Cf!Ii3#0Cbu$Y+Yl6z-%)Fi@{D1(-a@SaSK&2KH$@}bI8rX zwuTz{-nq?0ht_{?*b^fsLE_$d6Km~=ih3 zM+S=U-+m2lvsndiVO3C1BDkUOg45!~aPKSNJpdlTPXiX+d<>2@n=0W0`RYv5N&wgy zgh4D5&pROL;Fr>zmwXO|8pz}!0vuk{6W5(wF-*d}s-wiMn(+L`!< zT@-MMfmrCkj0zF^SPO5dCOKN*18ZVLNuQqs@3BXT4(f(o?aHS?ATsm100?JS!zL_H zJUj;#O_f98jp+^i>$yjop-?hf7{d%WY5o9?LUhsyQp*T}-ydk08OM52AjDne+~Tnw zOGPH#rQov)c1QTv$C~rX)4Nao6Vn1Qd{X`ghCDJy+!#I99}?~lfUkQrL5QQ2|AtCB zw387;DpH@S-EIu23MLqoxI9$W1N~^I`B3eL!vJdUnLH>fr-p_4g_p?Y@?TW#si8WF z^j~|u5;@4ZAG$qoa8mg+2nv* z0wesXWhgU%9*UT_dz@gF_X75v01>x9Fct_YvFlsYQ=o0 zUQzc->NACPM`0+LU=OxWQV{?3P3a06En3`q+>R20O)AU@YX^qg3g0JwxP zP6q@|6F?7ClxQo63cv~rjsvzw`2bTOU>qU_nsuoLX2V9B`4o=0!!X~Hz7dmY{VxYN z)2pDCu~aAzvr%IXIHkntV55QEjghaQ$b+)M=N=SmA;jVYI0%q>7KjH;jOCE>cdbq8 z^Ai|__LL6v^Vez#hbC*mEui8!Lr8IkAPCB-3V=rMOp^;@4aIushH%V?Cj9PT@87bA zCfTtljT4Z@s7we{x_gB|)`R38fGmvDf#^%&7!e9+LX88C`Y=Sd$A&`Q(%?pjMO01W zzOy37kX!Wf_`S(WgJLFV0E$0akiiTpFsHbS6#DY9c0ZB=-aE1^=U_%>_|WKu)r04lJS^h$03h?x7P@0N|b}oFH3OEfl{vaTtWcRA~y3 znO})ToX#d9A3@|?Ys;9RFfC%i4c$#c%-sfL>hGc-3@499dNtC*NXxq#f=LVtTFl_L zj#r>I#$p<73o~Xj@D6H6E>g7x3p2XGzkm}Ai9XCQ$*BiKTWVq%Dx;eSIW$0myAb0F zc%TMZb7DcIC;_ev;&5Y7+M`(jH~|DM90?3BpxQ|+f~_!3;x;(8kV!T_uH?p#bDx6k z;CY;n>Pj1URKVKSICr-=hAog8SBOm%7-5dU2)!T0!gLdD=}n-NN^vNm!50GJYXF&enmyfL)4;C={Vk3K{qT-7Ql zu}7M#X|89J7>fvK7=_c$gQBt&@;7J!f%f@ji1NNL=cIB(Mn#}|F25Vm$vw3p>Wglm z!V~urcrL5kDr!th{uvCOgc%BDD3BjzJS5&pI^-@qs6GQs;_kzS<6x1+^#5W7bbTPo zWF8U;gB<(r_zB5_oDh&TFqFDx+1Yd>X)sm5!01rwM@_8t8Y*wvI z&a`9E7Vw4x9F^`c93ERq7C9Jp0}TF@*H%V2qe!WgKhfKvO+vtZ_b#5IReD3i+GZY!-fDI0UQV3SoM| zI5t&N<^;~L3+=bu;AR{4JQ>~07#I=JdX&Q9 z7?F3^)=HaUxhN-=7#EJY?O^{H69*I|mqqvU_5I6tjC=Bk_M_NfL;bm0OUcai+JRoV957xE50>hT;!$0F6h;7_Le& zl|nF_KuMPf7jqaW3cJHEgz^3xIS?el;6~tM0Af@w^37EqJ&NFgAiJBbdr>QerCuE9 z6QBuE)YSt5*xv$h@XotM z0nX<;C_odo*>!Yz(_OYUoBlfZ(C5E<{d*rwvNG7`osYRc?A`Az)cmhIQ)8tVdcY67 z<>o`DKy(xU`vXMf8?dB@zyqNTw+8@cjf{(upZvYQ%7}r|7h*I{6hqJM(Hq7ws$&0@ z?mKXVRa;2ekosz*ug1&e0Y5@0k>Pv}0to-F-ac1h7~eHFt{p28Fihe0KSF*D)rd8O zKVmQe3TddL#NnRnf{Xl(T}Xlb-Y`hO+z-BBBJr7ER)9ttjDR2jT<>nUixq4U8bc$L z;PzlsG+_JhQ>eK@@WXxFo$8_-feS}-Q!sK87m5eClZ%&D29xvM=-Cpiz`P40##hbIz^Oka3oloN1(#&w+rpRKVlmAi!E$-W~fQfIUx|jV1Z&Y z?j$pWihghP!#TY086nz zil7k&XuwH9Gya#Y>sUxb&W96JDnS7r)c9N-3>yfeD6|rDU4%a*7%1~U!3>4E&}Jyy zW6p?s$cG);z0VQsoCY4FM#lzl2PcF99e(gn;{1y)99HiD zSpgo*G;kBiY=VXpz!#=(;QBM_Sz{4=*hK*un0&j8f=+?^T`PhD{tAp#2pJrQxZTCc zZqIo{bYaLidI(M%-VOP{_%`DBy`LE$`I^AMFD!~Ui@xvVVtUqi}ns852q6p7H8YSnOG&fCdlIFx&z0Lb{M5L3Qy7Mg^d-zt1Yi~*3@z~=0 z1oy8ivMoE2O>1Xb`idQAS3Vfw&W&bBo^{#T4)b>X<)2|IwRA+>V{=i$zC0g49;m;w zI=#SOvH2_BzOuJyXDEfUp8R5xtowFNdgBRm`m3{SStm>HOx6?&afh+VIL&X&Rcx&{ z@^8l`My+jsb?M4AsJJ(qji*zgV(zQ7d`4*-FXlEJThCfCS5y>0ZI2W1duuWnnXqiM z5b7DI>E_rS(4YMC=TUL5Hy?B2-YuvsRIc7z$)KmOc*IN?AJsjWwAt?I;upz^dmCjk zn7pje_;pJ~BX(msV`bUvABV!G9I^CFDv!05qt*<~hh?5SHjcNmmPrL4-y<(jytQ3_ z#N~*zYul%$+^}aQC$#S_%YPjdEfev)H^(@&B{C^y_VX}q^M{zXs(p|7=cgy;yb@eq z)Xd<{99{c0XE^9}<4P{W9+nyn9J2j7YGd^F`UA&wNShx*J zaLv8>P97_6RE}$8L;GjftwV^~uX%esKeVyd9F)Mf5a8l5wrz4r7f1LYlWrD9);O@S zyeETV-zcf9o4ZHX`0V(K3obt^^2*96%|emqZQ7huKN?)-%0*t?_%!!Y2A6+x%^|;f z!Fk*zH2*Cd)Zj(BrboibkuD*kR-Tg|DMX%Vy4@Jp{7g`S7v&*-BvQM zXWjAf=_|?>Gi5Dw+Bawu`+Yk?+lsYH?3Z-6`|?KM*`Nh4p7on;KI3yQh;U`&*TPm3 zR?b!iNyRsm9USS|j^GT#S#fjZY$P=0A13x17+f3flVz$*5yIKAZ}R_R>3#2rn*80T z9)%xT=xChCs&+oCwSCRs{-kkm{&UufW!Y1WTAx(48R>;By3e@m`8mzhBcIx(hQzNY zu@!E+li*}8FRwlEy4aZ#R>dU!-RqOe0S;fB!*XgC|G-6+`=%U8Zsv7j1(GDr_Jqpl z#cdv`ALm#qttIe`F8s%CvGhp4la-2;-@i!D{MX(o)wOZ^0K;CeCp zwz+=dYU{%%VVNS94QD4l^D*VxUjKDs;>+4UH;f;qshwl4Np`d4tRU%^D8zrJn;s`A zEuIXzCdTsOXmvtzC*$3pqTJu>5^cxE2!5ul&jy$mcpb;&&xVz~n>^G^x^H2tw|_^T z^8u}$t@KR$X*Ws|?d6p(2kleSkHu`J)JyeMljyVP_s+Exh?0zO|0%g4cQU0`{`l2o zE!)r0$M>_ZIO{F)J>FX*@wQAzE?W_I&t_toVv$bVudy^R)6qs?RpWE}h}Sz;?;Hc>aiRmZ6TA#>gNv46MIkO z|K{Z*8j5JQ7a0nXycSbl_&b5hXJvY}j`8j|ZMWMg%Qc)HmkV!Tp+tmbhUfX;#H*jP z>XsrNMy`&8hrM*e?YwleZ&m#Kk*u%HwcqvEQFeBt?;V^(S*l}WpB5;!$8kiXKN~1r z*cl_FMWX}15c$^LFV?KR{pcj^#*FC=8hP!t@m1j~oDUv$s(;mP#gU)v?S3ZlDPv*& zq0MO~`D5JCg+m^uH~7yQz8RWS#WAnetFr2dg#`w9UzptUUAgU-^QC*A58L636Vx?u zy1{28+Dt0QTRq@yr= zyY>&ion6V|aeuPsLdlQS&R>nA7Ms68PFsb$P`Lc_?A=~hrwg*_`%NhawM>2U9t#^d zhaMpjn57&qvwZvPpwiv$OKM9*F<(YS_-fzATzC@Tt<_Bw(;P@V`^ntu-k-;{9n3x? zJR}6kM%UJAFHSu+KSb;E;AG72Yfa-kcZ~oRezXyYiZf(B(Sr$6ntMP`CXt5h#Uhl`h z`z2^jgnOTtX(;?|(~Q`?w|TUK7s$^Feb{4_+lEzDT&@1&?tq(5E+?LQV&AkV z!TnhnM-p6in@UY|Y(YKv^I058^bPxr#=fcnn`ZiV6u9c4AHkoFy?f-UUdl)RlOE@* zQrAg&H|?;{tEr9Shj6|v^;>%rnw?d4cm>IDuU4%V*)&AQWE3me198uzYlYZ^+GD6f zH$tb0e2ylV=ne|skd+(}a3#iVob2s8Q*-!+tjOFUCSqLg+S`?*`wr(1yrW1X#|aOc zaC+tD3n&_9^BhydJ*Owl+&O>ohH}B6u9_Q8Uh(T0FJq}2%j@A~IG>8A0h1@1yVS_; zrTrje)ede=wNn+^>*Y;6{xYH*#v<$VdFa~u%1nR0dzaSH-XiPX67zhn$45HcE9^c+ zUX)vyURXLJY}Hd{K2Pg51XF_yOe@zyKRP|V}>(*08JoWbsnqE9mO z6YZM4D_*uW)9K94-haMTw;RQ9Xr_?`f6xwJ*e+S4BQ5V+4I=frF;Bxs#;M+5nKd6+~vi&Fwsg8+gn8p7q@=D+5b%Ah1bfn^`rZ=j6Mw4=7v6txRVwn zTrGHlbj2<~s7$9naLz#@4F1nkwrlR$kiwg6*IUjKl;X50qUvP_3vbzkeagChW;H@; z=St{6z@hyf*nl#vjNQ7w z>0Az5B{vDP!FXXH^GagHa{G-A8n{c4SneK^Q0%olKH$5$N1=(cnY5{IwI%Wj1KfNk zzP>V=T2eM|dMKKrr@W=0Gxuxv`ly7}K|F_Ms>D**w-G5BS4J(J`u>Xr%q6Q2_E=G& zJL1XtW>zbfblNeVn`3#)H*{|v&13+jbi_59*e8A~I(PZxS3Gr5qhi1*}e6;`ZqPS?-n&-}H%6`0WrN_?fYT=i|b!R_z_A9k< zYR(6}vhRFswd~IOLl|%Od@+JcFMZmCCs4GE)LT3}{B~4_9D#&~rAK&kP3m@_4!)l2 zcpBM2WNG%rBkmoE8h0rF@e!`(Iph6DIgs>rKy8q*7B{=*d{fZTk@!A;fvTk=DtSOe zemkz3DdO{!@V4rLtU|$U)0HW`hK+ zp#4PBH2KlS=i*fJg&k`aSW+mAsRc9kcplRXgui|B%NPm)e*D#kW#Ji->H14yOqzCz zBtv{(x?fV@C9lP0iXPOc+hcnFW%@(-el?Dip&+=H$n^2C1koVj#8b`ofcVI-O6V|;#OlRBp%`+!q~acD|8`9N5wFT+Rh@Uj>duQ7U$h)EEf5cQ5uRa+_mci|sf1;ZDlPF)c!m*vOe>dJ zAySdu497w^#M8_ZFLhS_@Sf%>p6_J%;HMVbkGLo8_lq+f@KKDTP1nzR-DQur-!Dz( zEnuC9M^V`#XkDu<|)V0@MXesM-Qp+KcMAc|HFIX84-l+zD`-+efz%oh30;g zQf=43Yd!ZqC&QogQBsu-zvrIX9m=nORJtnl7lWU~u}h7um;D|HM{xN(6PA5s2NP(L z`6o(9foWtzd=U$m4g%6~1B4aYjNOHVl@TV0N9L6rCt4Ac+?huVB z0*OV=tL;P%v1D)Twb@{c=S+E1i#BTymbRxHI|6~;t@EG1lk+ieH#}z>7~I!~T~eA- zklEPEdC4|tdSDi9QgBSdu*I%LYkgEERK*MU^<`D!YtQcrD=J(6Cm^XByF^)L@WMRv zSy;)eXJH+7WlNa1HNUOvW8b_k6Gs>j`>uICBgr-?-k>?&bN?2St&sMtN(+}s!%rU*i56>;RFH$UcY9>-2 z1Ma9<2!EEQM{Iw4@l7#nJOp);-2dRRH2q_1((4Q_Jsl!V#O-t^Dq6-PFKox!FpwO} z^6lFNNqo{`9Jr8>bN+}Kd9}8?IHT*PonreK(W3jD7Ewtv?k~-%qVMfv!meCbij8hI z6g=)@ZR39c?NZe`{W7V%l=t-W#-{23v2E0dzWv^N;Wzda71Lff(V6w{L)k6@vdAzV+-5~deDTkY6r5mF zc!&%W=GQ{VH*2{|`QrsMyAmj)TC4;wdHs%?p7yh?W6IbfcD{=(cKtMLKm7+oXpq4h zKiU!UuWhm7#`oI4z!n`zp<86Zxn$)Af9pE+DG2RMFPZc${}R>tSNh)k2}p(aE(TXn zneJS=w14^7c|=M5#kE^~S;;))HFT;pNQO`|bAuu)=JRIXed!S${#$pH7IbBDPExyE z$5wC#>94Q+x^+G;#urr(emy}(rV6>beJNZ=QqJE)d%S$?6=0*~p>j!DatJL+u@GEl zZqIpDc+~U3VYKAP!$G`u(W@Tkr@`2FE3#xW-BS2oa6|~%<6G-!g4xU~tKLf*d##Z8 zcB`~;md2IvgfOiLdZLTAbl{`c)%P=>uRdf(TfW}UbPaD&3r}?H5kpT@wvPGb9er)p z+h+U|NxrFiC$t*-zV#U~xysho*leeNwKtUEh6*q_EO+3PNSXOnTd7w%!K`RY<-n#n zbbsFqbU#t+dWK)p)xwzxYA;}^y-lf!o=^ZXmHV*4(@uh(XM!dT;Bi@{a8SKu^x~Fp ziSD<;RCk)=fELOVUh&z5!0^Pu13|(O8FXavTsgKt4Q7oI==rc`Ee%tKu)ZW!6_Hc6 zdh@XY$lYQKMdU7h%%<=8Fmq+gCx8=?qdu1Euf^VS;R-`-vnnmfQ{VdQLsN!KpPm`@ zoYE7tqu)=PSUy9^E4D1$*VWLL@*S5`j&3*?{7?-@Iu{9#G+3&}HhFFF>U;8by+Dto z>+C#lvXfbRv91^FwtWcMGSp8aw%kBM_KGX-NYMyl&7bafMyqcB;n&j9X6H^XV;jW+EI|9q<#6rqvrT*D`*mZ@itIAzN=KY8^ovb%YI8^ZJ2RpS*cCMe`>Lpe*Vr0A zYWM*fG8|qoP|P&Cyojn7yiO^J9+4#GZ=7!EJG7KH?McIoaeN>}Y)Gp-O*R_U1p@-C zGcg)9tSNjFZRD!;y z?PvMIM&yv8BA;;PQ{*04-1#)C6q0KR#g9_lk09ey1!RN4@4RlT^g|v@)HP;%PqG5Z z=~UvdxuBsyFHmn)`_}ip-)5NVK#A`&Kqa5 z3z_7werLn{oKR9JsT)eDQhEGh>`xMrO~Ej(`hPiV!Z!x|GG$dMJ@g&B>D^o z!k?RkeRam?%JSS_u6Jb$?CS5M9d*xbM~>{l@9!Os)D2k~v*jOYL;8T<^ARPu!6(#q zhqorfaE&xwEXm{SJC+vgULm)+v*9^npyKqkXGhPif69=h_729h{sivBlWRHk9N*R- z_giaKs9wi3PYCidy!D#zy)5pGMIzPC_^U|%7YsMVk=rd8WHl)_Y8A2sho7xTRBl_( zloJRia)8|~Wd1gbwblLJM1ejtq)8Ropm_0 z*-`nQ(kU{9XFBijZc0Dq;MzRc5g`1a>Yj8uohSd7>9_VfRx%8Vn`KO~web9xC_p2; zzAM%H#f?||@gA_!yie3P$>ZxKOGFNRkH9tPq)lfe&OP|fYA?YsoPV1ZK%_0Dn@lI{ zza@LXXR5jx1%(vn%dksnN0%EfVRxICiQn%kY*;11Hn-#3P-m;nQz6U!Qbxa%w8|f< zVPz0cA*bNX({7zWX2Y=ar8*z>1NoOxH&YZFFPP-BhCF4_0C(Z)xX>Y(a&XDHU{p^j z_oE_OHdAK6k)L=co65)WAdez;DM$EZc((?3uR+-PNKVz=os?2q$aF)CWN})*1f+q> zW2QZB8GJhND+epmi0)-a^TLa_ zz1glu1UjQ<41)AI3)~r$4Z`XS(opf+@%81M*z(Azn-YhPrSdpXVmjgrYn)Et)C}4S z->4m)ph&^2#f#CPTZ(Qi)W-wpJ7{foH+aq?7|+G+ug4CtktlP3D3;Ih*NN-*VRMoM zk`&^hS6l(K6ls%J@S*iK=SHqM`Y$(@-j2RObC$9B-Vg`j=Y{sk#SiTH#S5N?ab4y6 zPjzaoo$L|=HAuz@!}8z~x)AoalAlH|oQEV-{jr>YPH-2WCFuxl^J3Xc`7mSyJ_$1!d zdO1x`iqWl~x}Y503ARu%7hTC?=)BRkDsXwRDxO9xV8#oKa8wB*)W8`wybME-zIYpP z?6)~3y+;rWbRHTVFDTwAo$0aS9UD=iV#zj9oc{F`@pWLIQFM1-F~YVx*{RO2QPmZN9BJj>ionDcw^BK6^hU$qNjQf6{QifZ{Q|L~E zk~6^907&82XA(TSHyoT*;1&me($ns~y$7@FUyR;Po4B79?aVQa;v~!Ez@(@Hg{&cI zHo%$f?@onv5|E{aT58cI3pToBKdeMGT~tA5CIpvXgi9_qwHHqPI(%<0=%j8y_UZRT zB;kvo))Y(soa}mqUkj;3H|_i`UKJ$+F;1x-b<;h|_3;ib?Lci1>t~I{yY`<@3wNH~ zd7|53ig>7Iuquf4w)7Lw~SI^<3Kz}(q zv8I#gWpNBSY}TBhpG&^W z(;Fi^D$|x4-*Rlpub45IUP!%P`z&$4NZSFBkeG~#8v<}NRqva0S-B(PU++@hTAD$A zBHoh_-rW~?L_h5ONUdrcaeM6^L$4JyJ3IVE!G0*J!a|1Oax&XNcBvo4#`i{ELat}=bwDC9^M}N_keQ={T18%U` zv2}FBYGKAVA2j|1p{m{_D{lWE$iSg=Pm!me~APmps;x4kJKMQR)NZhZajAWdRkVLaQ(=a*Y-o2^gVs6gh$$#v`#k+C`}kD#;d@NXZfcia#;uppfmNDsomkvA5RuV& zBjS<|MY-jR7vzaPAfeWAit_u{M4Q}rX_wg*)IVlrwY3U?-K`WmXj5j&2bh>$qE6D*fLdueON6^9HEU?^h~2RS+VkzjK?zX-|vBdhjb-m7=R48=hZ=|MT=P zDi6!=(XOn8!i`$cY(b<`ir9Q`$;R8!)1<%G{9o9ryg?nZEvH>b>vEzw#FE8cy_SnW zz$#eyeRu|)O0B;ZLr;C1m781v{-Pj zF~w9oqfRz?30)Q&=sHSRjw|0ROk_n)G-81UyY7S%ueqvBb&AJL(;tyB{%f1^&;*r$ zrcZO+$!oRvho0{3VG)T(8y+g(3R3?;*N5p-xW#{FkO*tkUWiD}h25R_0pR4}ZoajX zl=nlgERs);^Bmp#iwG}yCGPq~hZvSeo@Y);Kb+-+{tE^OnrTdZw2|<1wCR>{@9d#H zi)5&g0>m?Elu*c2ACr^0ca|bi&!5Qi957=ao{>X%D)V+E&%1H@Ye>H@bI}Kvv(Kq` zwXb%nM)EY9zTdV;XO|FwcCN7~F1s~V-?!Ncr z21Yj%hDAuHcs9sp4r)w@xMp2=?JfT*z#{;?3fe#6=i&wl%&`FOjJ>b)4Fx z+U`_i!q}70`$Hu7Pow4AQ7Yb$%dDFZi{1Qj_#;}+76jefL$`R}(}>cWl+1Js^r{po zMn`0{L8_b9lv>We$6*&g5~cs$!1H_iQmILp@Ifx;2d!fX%qiw&?JH6SvGg}O>|-7e zzA4+h!Fk&9>{9#-1)J{z%%Yl&s_dFNcy|TRl2w{x$K4H01tw>km>H)| z2yYfustl62H%<>{Bf6|tLO7X$6bF|r0*nS;t+oxob>Dg%&^UAB%rpdsw z_P+4JpYJb&iXt`k2;a0$_=5|4l;r>soJrsaTXc6kch6y`tjfN#4NXO6wKlI6auj`q z4{~0bz=`{l9=W5_?S5wY&#=YMXDjQB2L0o|k7=epjNvYjxXOC#@XHwr?olbn(4~X< zXQ%pR6TSRItg@Cbor~jYyEY+OU3P_7uIi`V(=9SKEsgEp7B~M%ez0y8#Ej=?_Bpn6 z|Ec3Y8e8YjIH#JLuiv?T=QW#(DBGX2&ksHlZ>Ge@`9@Pc-RsLVe8w?ieZKM{H zaaFnG*)HaHdl~R?=e|3oG#3jEn2Y{%J~Y+aL-lm6 zar?lt-+nEJo<&a|pE5h_G;_axA*Z6E>%17inuzo2giF;AuQN0s=NO`*NBD9A*+&B6 zBUTJcO_J@keZn)gD@C*E)|)-frKyuM?Y;i)vTVcpAG+nfRhMOhd!Bu|sS19=cL%#( zED@V~g-1<1`RLOqNO;!+edoOXx9#*##)G-Z*YzHSXAD4dkk!PgX;cH%8arR{sZPd$ zoR{mBS0_fNq>i|3(n1=VVLQ1H@y_hb{o}FQep8c_os5aP3(cRq%1TV^ht4HaMvvW_ zDV;Y<5fgegIQvA6BDIoHgZ#Z$rKgT9+vCdR)%BRGT>FA~G#|SvqzO7+HoC)f$`3^a6)>lhQd|}K6wJ3_ESt-0! zmpnDrpI+Kp8@MrJ-1Mn2YliY|@yZnB(JS>Cde7)49D1?~%FMPVz4`OmY@Fc8B4Q|?H$id@+t(ZiTmt4s!mp~3WX!mRY@ez7FxaSTCPuB z+%Y^lR5-id)goFMhtm5Wp64XO$<&jLIPobqlCGqigau!~8UWod*XMHU>TCEfp;mSh z9yfaUC?Gn_SX@eL{Bq0fmuB~Vb2+=a>uXDMj?u}(st$6(Vgv4_t$-PP)u-Li#xYUelj*Z z&w$17-51mt%nOP^Ze_ZuRiqNxMOQkoT(J6Q(Yu{f5A@hyI zz;NlUb(vGlQW1UrJ|1Krr+1OJpHq=+tbL?!>l|!Qf8Xd`R;4P%qy&tA$&Cm0ri5#|n6SCNErr#-B%G}{( zOhpaK11-DLoeR%e)rjOsuPbIR75#27x7j7IA=p4cayyqRbjB+{9Nc6kwbOLz&W{hcd~MqybqTW?Zs3e zNS9~Wdf2J1tDp&*{4+O=PVB?f$YS-d+gGiFHkt5bw3b^xo^BP!PhfsQlAOKFY^)a2 z9iP`L)xKf}Ir|bv+r%}A_`(buE=*d>8nvFO^~7w%vajkC$4#g64su_N`Oab2;^}O| zV6c5U0gwd{lC{kvn#<_Z*mADK1H$Eu`-0$nSgk)n8o#sb7p7TwqaMPas?nRM z;Eb95hu3~}D%$)v+s7Bop5n=OdG=sXy3S*J%VV`ZdsVPO*lq6%D8Yp$Zl1+y@dre2 zi}5*fczB*bexwm9Tz$EOW~=aGs)l!k^4_#bv61)ok@dExgM(8}UQn4`9JZTK5vRo8 zNoR7>6~Jlj7u%erM3j>f4&CJRlYIuqI7)HVV?RSaDDM}MRwqIC!W+m^J}d9L-5bsf zH_utp5fAxMi_*VGg`CqLLil59$)su<)kr>-bD7L#|PjDSOk;_ zXBmy3@1zC54{%6<_d1jgtI>hRlXuidkP7L_j^8;ZiKaDp-=anSPZsb447Z*fNQ%WY z>09)1?&HnB7w4Jop_UF_5+JOx9NAs;EN$%A* zvd*|;^!fc14`%i~gzTV$d0n}=OuPG9Ed5N^&95r|OZ1z=8};`TWvIJ>jTV$G{JP%O zod0{XD^IB66Mg3?=<$)V%Hp<|-t*l?%{VYP#(!vK;q50hi$s={%VQ_BA3j+lp|i!$S9wV zt-YyuWw&2awaR88XdJt@22&NROpCNi=^0~A%d zo9d3{PVCQmQKgec_9Pu|3rE4zm==BY@JVi`hNjAQHLj4ZFLG9RKSdHcJ|~bw5c*gc zpYH8KLoRS*Y5Z8`UhBFo%&zR3ap(IVeE7Mm@bDAO`oq;mcmBb6Q++a)XF?@NNW(=GVZxvMI6%@w-f=p;1HC6JI2`Z8}#Qem+S?eNc` z9M4^(9#_pjY1G_f&$FvH9KIy;l)>iffA=u&4MpeQN^$Jx;tUVvRP1X0xT;QfVd^fp z!`U^VE1rd+AjUySDUZR9(Z77>!Ixalkd!&tx0MF}>i^ZeCBB4{{`|1| zZb4_d9c7^v{OV*Hrr&BOe3DhBzyWK_f9CY`>LKBb#r9tA;C!LSr~ohemkc*lPfjj=*Dl$vntl)f)J(a^ zJcZeULsRbah0B$zG^412j^K(p35Sp0_{-NWxp%>=8SNo^Syo}ICi2~ku?WUFe*M#{ z!=sI%Mm9U|um@Dz8Sm(H^;#&nK6bBRa{!@aqcFXz(i$nL!vL)4~dM1?HNV3Bxn|U5&S2nUa>%LiVjXEV^ zw+j-Dr(=efq6cr(%(9up|LQ%Gdaa}%<51vITsJ!IY1v%+Hn>{waoGGn@kaQvE34m( z=dKec=2TWp(iCBB2id+0`5sS~ik?NgG$)^Y=XTlra&+&%vJ`(>4;##5&fVb@;w_iG zlk(qoq<%I}<@RKt6DR^nYFccv-Iiv#u0bQ)^xWD~myHgV zGJPcoW#1du4XvDBcFO4+)I-nK`*+EWG>LlXX_dmHco;j^*O`k>?N%rU@D^MKUQ#p* zWvittU7PDx|K7mi8HC?%+pQb@8RMVJ`Mt!S_M^#7+{X{hO8 z7Q6Z<*-K*Wg-PD75xFib*l&6yam(+wadbJD=qb@k2CYmhvFF5g%m*U_sha&m0Ag0Z)PsdV}J9jL@ zC=O%kaZ(i@O_{gN*8b=B2@D&t1S3}4wVgWKPtn+hmKTJOAzRh1e#W|SSdet<##VB< z6ZusvN*z7b+;AIUlm3(A0%jGV^kUi0KXSpwL0o@HX2AKEQElT}^tyytvhD|^TF z!NpXp0P{5=B(UMv!k9ge>_*1TaEoZ5Xsm+XqTtZ9ys+RvH|1U2C{YGY8Fb`n!s>wr zJEd8!*eeuilN$Ip=v2>HM(bbt5~refEIxm@@bupW7b|XUT`ZKiPmM_g1;(hKcB8j| z1nJR#MR<%nhffi`E`>-6@i(uVFkkki_`3jVkj@?5+CA92Tm+{KVM~ZS&tif||Eg%r z4zR>AW{Ud5hzy~0{h?aihXKY2bo7Q)RwiAw&ZgFaz08L8cT52*-!`P#y&p?xy6|q3 zv?k*@di4RVcP)rr>T;2Ouv;zw!`%*3xmwM2sFTA~aun!6HzE7?`B-aDT!QsYi62<1 zle`?4Y5Z=xhM~Td^)Cb2aW@fkMG3nP=)^IP@K&C*%F_;;d94t4`f#SFiFAN)%BPDR z+}V|5QH_F85PYZ5AuALV`vsUTlU6lTx)kqbS!!+C<^5MJENZ-;p_dQ|e)6=k>MsSU zIyTJE+j<^@j2Ih8mA2llD1Aaw~ULt=ci|Vp3O1adpn)*7J8DIa|37m); zA3p8v21?KoP*gWRy`WM5OZWAkqnqAtC)P#$)lEV{{d#dM3~sR!={-`xW~T>hcE0g^ zdvNH?)=|*4|C5*#S7HsVW2P5*qU?X{Ohio+kw2?o074N;Um0IDZ7#+|v!wsA-K=y= zFU_qFj3?4N6Oa-R%=gbjc7qz;U-%VyeW z$Oo^6T6FJ3Hat~A*7EsjlQP)URd??wag>UzN1Fb2lwDB?3Z^4L2B)vYx@IxFcq>25 z-1|y@@5k3o4G&6IYDeJB41t$Q8G&DF2rV*y0r_M5bV{-+(?%f4y-8QO`UhJFtLLe z*%dm;;tLsqR3d*wj(cBP6HRWD#ktTQTCO0qmMT_$zu)UUi@g*i!V=u5S0aGrJ5LhGH$ zZKkY0CT$DL#gN?YVOr9L6HaFA%VW+kU)Wy9Y&{r95= zr-SF>L>#jvUJwF5pI3;Aea7%`{-p<(#WUx+-_INTnib>TMG51)(YK*UM!S;sl0@k!2Vqh5+`gW7k@K+%QN?mT{@)Zwne8)8B3QJGx3$wI zWBbWoHj0)cJ{E>M*`Yex~Bb^PePj|^t;oX`8^prBf;gzY=`8Ik(H}2*)>^)?q$D4C? z-;iS3q)gO^qC+gl6GuNUArq#fez($J8CA;(^D?Fj#e{Kc@~p}{+E>QmR;+D4Mq=rq zOB!XZ_u8!b)GWxA(zVl#c;*It^G{*omb{fHnf>m$tfPWs5}y-~`6 zH&5qVU2kB_X^YfG1d0dClsj-=T)Cd;_P&&y;U}g8vKP@^K9N-RW=nT z6nsqy_{=A+)IyWjb4yccmJ-u-cZpE#9!*UqY} zTDfxN`Kcz)qMG<$=i)QGCNc-zfC+A5l?+fk5Dm1s3SHUIT%tBwQN>tx-$UbH>{ycf zZLn&`x%qS6IE79;?++i3idND!kZrlMPQrzzSan(i0Qtg0ni|XaoP;(Z(1-3&&W+oJ&nDR!iua_0Z`cG$mbhQdfOO|A<{Gdr@KpdKRel$xj=>UzsgvBhp;}KbE%OX+F+*lXm zAPY(Xf>W_IDOEBo_$p%#e5EzGG*+FIaYKV8*@Wq%hC}pK6i(Ta)RNSQqHA2h8oJ_Y zAzgo3VFT;UK=;}YmrA%{Sn=ll)Z@Ytxv>7C<*as| z^-p*uGD}Y`A}t&ogg^*V?-oeR5cp4o1t{6|ln-5zhpmUWgcmC8aVw zur8Jw1)fP`y{5FfJ{B)XJH_^m{@1u5?6Sa#7>Bnh_RmD=5W$6?K_$b?O_110Y_)<)fjVzV!ReJxfA8P7erNM6r7T}@EHb^E7BK#!E zS)VuF$u;>}(!g*vG&Nd0#Ub&-;S z7fdUtxS+6Eb@M~8g2`#QW#UfjUsjz&iyaG`jw&iq?Auqsw3ay_-8Zy43O z&J}ZH?9jP5jwBZ^bJ6yI0pJ1)cpy+qQjtWW(|c5^iLN2x;-HXP^leB(OTp<=DQ9gv zbK2?I*U8*$W^Chb8TgM?Je|_8b?F!Qg2}Bq1=`Ijy;qMOy4vil%*Ns~w_lOY$!uNO zNfp!4JsdyooLX#tmAX&(R&wIbB$P(s85P7TnQycKnAj|AT)jILFZZvPyH1ZQVn$hu zD}ojYvJ0ly9)T{oE*|l-1=w$+`+~O;K>}q#HF$qf&yK{@7PyyLw;j2~Z#-I9>3clW z)P{%0my)WFz_^=dp|Pe^><#Hf#g%1MdY`3~$Be-gS41_UB&KB0yMkngnLq$A!t)cZ zM#`RkNi;&vz;xvhlfDv7~K3E(hY0e)2j?Mozor zSQnYKV+e_QYQDdDi#zHyFSEg)DbF$LV$;ZHAi$ZHRvK5AioPu8y6oxK_(JaNQ<8oc zt%_yTXl@y#vZAqt`gPYVKDL*cjs%|8_@jL3*!`@v#ESq%b~h7|FnlxdX_U<3ZdE%I zL4YImSGhk9qY^%9j6#mwhp?r@)o-l*>0Uq|BD(7e_h}wouZ)o}ft#`}5t}Qb_-8Pn z<<+Yl#4M`kmR~^jpnSKM8#^!EAx_d!Huj7lc0j}Q>8OLTl#WgQ95bB2%OB~qh#Ox; zmRfvylZ(C245j6#yYRgKXBG<9G8f0TJI+Eq zLFm_8{@(B?u4Lj{*4AcN`@m!E`3ywZu-L2Ij8W->Es!26pkp}P8#_L-McU8>uDaW$ zV=8T`3T>Xpvx_)#RX!^_FZfian~L+6#FVM>U73gF(#A=_I(ahs@h7`7;r(JK3q^ZO zKom&RnY^XU!FXe7V|VSS;F&7V`&EJ%(>1TMn@)9s@}|`CryLsoN|7M3es1m$+K6|aax8lriwtB;nLNdpvi$Z?L(w#05=i*)u zfzEqXX_>^@Ox`w`&Zxy|`M{d^0aIt$blbk6M5-r}f3f}62Z=83Pu(Ho)7R7(p(9ZUV`K69v|+JmTPtIa0mss`V^xE&Jd85bqUi2s7)MLI zR(;H@)pgzB-6SiK?15}z$t1arCN5b@M^ld=IozT!!7Q9apCJ#EIl4Q;spHl$4vd#{eT@VxIyv-uLJ?#5R<-0l_S@h- z*VgeZeC(>eaV(2Gxz3g}RuB*1WUl7hyHz16xb!SV7+Rsq6CF`zD!=^7N&E~Vm?7NVEJ?aLz_-_(7xp(e|j zHGp*`h~tQpT1_M-A%cAd@g&9h|JIPZ`Q+f2xE~#P&k_Mnt%`Px2p>76>Y=QnuAcen zPK2d`Ua#U%=OTFL)fMTiXjMUp13ujzBm2}mBR=}RFO=yx6(;9oEgJGU6Q2v7^@3ci z{JZ0lsH&58b})ZM&BM-J0V8vG%ZWJUd0bEi2{`#C4CKoV1VX>iad9jAHWck_eqv<( zV*=t{-8d03>@gv^r&>J9>KH=iqVN_Oxb%F4Ke}bqdm;&N-3!Y45>UL|KF+a?IJ!5( zh;iSMQxLM6w(dhQvgvFy6}euX9Sn!F07J$vAnn$YjU-WNzhl((_@b^jtvF7i(sb+>fy`@EG}WZ8OO0&JPj!vko`=o0@$Jh{#k14>5ja69iV< z*mtY1BiAlC1g@a-v0pVK6}cYlU{p>sIHJm|#%8&cK#f&Z5R`EJG(PGeJ$6F6_79P- zMfHl~(lgx?Uq_izgMy|no(>DZ=rAR9v&=3z7~IH9#R-Ay2}!MC=rdVi<1wDPh|uIJ zI6ASWb}=Y?hF&a{6u5YqfhpkdW?Pj@^&hA&0*PxMW-XR2W&4rk+4Pp~9zuH`4AFqW zw~!h~++^am5S#9rXfso82IxshId!}w3-L3CxYP2vv>(~L%dXnf(WBZ#X+mQhh{!o_ zB5qu9<>I4tUJuNDE5r7on zoG~vBJu&eWx^ez&<`_YY*)$j`z8en(q~K&rJ-r#uz2>{86Sn%bhS=p_XeaZr+grF& zMIpA+Kb+L54^BO}>9j8S+4^jquPMi34TYo?(Qzo0b6V);b;1|m)zFHEOEN9+v_h!& zU-x&}!frXFc@*c~`_|rbaE-XbemZF4k@W)nZDb6qAat2qnkk$#NDu#OWp^*-rD6kX zOel^^C+ieeby`E*azYFkE_jB$gTSK}F#6Qa%``8*S60PN(-R9BW6R~egWiKe*4CXL z)Q&FY%Vkzo-b1XNWh3Y=@d|9RaE~K4wk+qM6MyH?UsO!D>CWc6H^??+uaN?gkb@kh z^td&&oio-#_xaq@&smcHID|a;`yzpIYVF)5>`e}9p~alK+%saCYS~tS<9_?n7>n5Y zS}x4D-egI<9LAf*7(sC<}`guDS`l(Dj<<$wsPc_bL3T@ zhXmhN1qPbCN?GqxzJQ8Bi}11<@DU^DxI=eVRV2qk-?MGB0*|8;SkO#HwQgi~x?!tQ zyeai#A1W)TeaC9~JW4OT_+kGi-WbMSlG`=}3dRV07wr?x7)CYlZN!y%7>7M6ELSJU ze@*^4aLgL?7(Nm%MvP&Le{4Xk?t^s`d)L=;#JASKm*D%8X{7Qe1<~g{9j)qGuxl`f zfj&N=6~n$8?cWMUAA#BWOb25uy}&A!8>H@5@5P9l*qw=mdM%x=&_-hl&fYA2H`e8k z7b(Yr?%mdXyDLST&Ntmb-PQ)SodPxYCcEGwgCwkJq>HGd^{gCgxvb%oZTZvR#(?8{W^d%e+8L|_G6&-iVqK%#(@1aG zQo4OBQz=7^J@9!Uv$E04Ry9NHblA5`3Y3F0%?-rzdPdGLE~B;s)`G^)IAfWZS2v$R z{X4hH{j3aBHB=ViW8ik=?qLg(SDQE~iN)tMUB@3SDX58c#5zrDb+Yn9pw=ZVeW_k#@U|f)x8fnWXIPeGLE;Q;=qL;~0dfOzz ztwm0#XC3Qz3DRQ{{%@M`{6s&~<{VECO)nRN2DZ+m@~b?_X@J+4qbG>k0~*q1Mg-DG z_;aZ--@!Y?<0l(@%zd<2Lh`&q$i}Nm=)2S_D505!&)iDEpudqM&DDKEHTmQT*2-o^ zR2@F@U*1-?b9>qk+QPIVAp`D>-y0oKPpyx?bmirXq9%WT(4aYZf-=d_w4 z!@C?uPZcwp2`ZJN)%jy|+~#3Eer-1fgU!rX%vqSm9)&vs_@e1L;0=O;@*#5zo%UGU zd^0od6EV_BHKWGJFN?P?cI|O|o=?85OuM!yw+F}SBAHvEk!yCQ;^80A7MH4=G04{A zmf!kH@*xl9yz8VLg3H-;`6J$`ZV0+SJxeTWM_|v>adECQsle=x9=o;Wt{$)1Kb zRKMB~y?4@GXfWWD&b#hxTc`ZVI$}2v*JdApbY6s;xt^VBk&S+lzzO}XcHYB_jjI9P zL`EHJE!q59-^+xLahYY;yL3qMfO|SM-;Ot73Zna{=abH;KB1U3r_-K$NCgX>NJ zeXfMD+_ZT-{jqIy+j|x?1Nt46S$D>VC3;Z1KS@km2qnImlO>Ujc`M>3ku7k=F*x6z z=SdgkR32z?F+1}f3->B#aT)8WaNa8prSW&_yZ#v|86Zc#_SqEj9ybf;g|(?enlo94 z5;oegJ*J}w&7pjfzI~>3JzP)J%y&-lL9GoZPlT%>7=>FN>c5doEFqZp*nMdvG>ZSc zVd*?LFslp99&BjZcn6vDvfU7E62AB=f3~2o&H~RwPAU_AcJM6%q7-f1Y>KUZ2X&Y@o-!F%d>Z zb$jCkZ$ebUM)fYlY6b2xpd!7NCSXsCM8M>CrN}(UhhRkyj)B}Swk~$brLI}|0Tu*h zhZlA20k-|{Q8UU!)4A$J5;erW%Pq}t{Ez57nI2-NUQ;tdyPsh_hKYzBUW;d1<4YuP z{$+UGBESP8s`#t7m;ynfE6LVvAB*K5_N-Ir>t1Yp%(Lkd_<;MUV{OxD2_#*W7Rli( z@($>^ECWrf5& zJTaO+sg>z3Et*`j#Dy23qF8t zIpYZbrxQWN+k=`)qY;90*eHR$Y-{yxykK5L&&22>{Zd&AZ#+VDItzR40|%?wat6hQ zX&loDms6AYyGCaMVih(s`_-l8f@v*V)r*rp0s{LvQ>LP)QNnM5|KrFQQMJ5;!zAF7cDHe{_PbHVYON=M+`|y z+W8c!HOf;apyZu8>Bqe<0G%t6*Gr)41_P%eB^|Y9LG2Bm<~H(X^-^Ky3;yme+B4x| zCG6De>Af&to4yMrh2T!!j@7UIsP`oBT@Ud(I4pN}<^9OZOfBahG`A=^;iC+HHTa2e z^0mPIgr)g+6=)Xdt+K`=>()5g8KblQ^cHTK=#8``pw01@ZSI`GjW6~XLfyV|BM&Tokv1+pw^jR1e%|Z!?YQmri$m%*svfY-s__P?xrzf6}__mS9}Z^uV8ddj(ddn6bO^O{lpY)r!t)Al)j z!mv(s=@e*q%DBmSbKS;Y*caG6P3u#Mi^trRQetMWwKk>>e9G`fJY98umyCwIs+?yE z#=khkVuJFv+@gf)e;rr^rqFapdJac^*s&*> z+#>jvQl%;vSdy+rOh=hIq5Lej2y4GT3)50#j(hW_HOsPcJ3gMbi%09~f} zq_!?_I3|>NmIYM*{6OAg`a@S8o&%iNBrn;6)=CAx53|hCGgoJmnP19ZRxHO!9^JFM zQ`0=W&r7DLheF=2vp3IiiNyX5?Ckb3_@MVm5^?{wl!y}YL6AMQPw>028WC0v08}1b zBfNPBNFNlo1uK`T*dkG214G&t>%N^hGJ8ra4+vH>K8YZES-&^}$JX0YSOd|Gp z!tMLLj(p;JDqo9ZtS#V=vL9NH_%q`qr4;C4D_iHuX3sw7!XXm}LQEHH#M+D8s`0kY zsU|@qx+G!c(oSaDdm?3Dg`4FT>TLFNR)|8&uY4!U8|ZX#`_I?Z>(j5r?>D%@3)7%| zE)#gmyv|~pd3YA$OCJd&kc3rRa{isfh?kk7UA%cdA0;?2I@$W;ltA*yUxY933VYS~ zH~{Vs3hM^goTiAMkioWSPIDp#f%#`hKu7`@-u$8TIScF>B~=y6otI#2oQiFUvDJ@f z|m!26`u%*2OukLPRhZ<9$DT(DfJv<$iS zex)xk`IBf36jjlv{hd`H(=ntIh?2F!%;iR+9rbbR+|Ek>fO$&z`enjvM!Nokv1+El zeEFyO?_hypJ7CLS^`u(E`(B>qzkU-uwUqUyDSJZMq^U9BOik)#4{^A37YBfjYRDM)P%MWvI#s&$063b`L5Vl_3GF z7+Uz}1j%L{G8&Z;BAJ=SQa-wbVv%_(x25`CkId|f1C}-``Ur9xWYxq* zS(_iDUo<5Njw|$65-Y?WKN(qTDZ=78TDom@Bis8d*f}8J*r59LhHM*4Gjor%7JHJ6 z7?I)OaX{P=swA_#!tgBRo_{l=LW5EowFTzX2X?Oyu%p#@*0Mzg3oCCr-o7=453diA zqE(ILj0roz;BQCC+6Ki*X6scmFJxXlHWVX*0knG)2yS?gvNy-N%(a`0>BYdm(7Gn3 z-{yKUw${g?&DCSxf8seof1&U;Oiu5A3kDsoON~>7X)op^$v*WXZRD^v-@)0Fd2;%1 zeNCy_bf$?V2;L+lbw&erv^uVhm2OF#*te8x72`3FJsv;RGw`^`Y?E+Tpd zorvf{1PN z;oFot_r^@9Fgqw6^M}&kPa39*WUZOHGFKlitT6iXeQSrTb3LK()+2o4I*UCVvFJh2 zvOv8Yj6Bvo8LrfHJGp0c=^xf)erfBFxl%tKO>*~E_!krK0dgU8?A_hFWZ}H5%QuZq zC#>vt0 z`9Q9)Xg!=ny$QRi`!my^6D1?`N%E7U2}u*)k{q;!D>~p<$BTz>v}yBW|Iy%@kXRPEq&rij|~Q}sru!qBAs2n}bYdp=2*H(?x- z_1vAIf<4mRR5gcBC`Okz)^CJ0DvjYXj72wsV>`=OspA6rrD5WnW-_?c!ioDCs*d46YU@0V%XPYX(0KC zCv!NysKE2i$Wvh=CY&k!v-z z6XB$YJ!~ePJA=cf3@}Tno$bucb4vkJ#1-5I6a~s2Z-vx$SB3}m=)g)o)%3JH$|9$T z88oy1EE*?T5EAgee)}Nc;bslesT^~T{du=PXMxGbt{3t&ad$0@^H~{+GV#Ud#Ofe? z8R?_j6P3Z{j=%!PG2Qx#>rmtm5QG--B1oErSV#Jr_NG_p}sl7#ssq0(vcgw zGm!ya@x2Ddo^q58v*P>5WNlqO6maReXGrOB<49O{mf5??MS$FGQkZ+KWBhiAx(H-N43VCg>TVdF@ORrZEP7p-USg z)7l9~4?IGdL(A}}uWX%N#KB9Il%}$w!^)it!ApZJTXgrvi#_2};4RIx=vopwMN$k~ z!hJt7HnK6~?Nbu685+Z89pI5wr}T)XnvX}W4%HFb7*e%F;9j>vGcA3U&HtQ~CFno` zrAQjRdVSNE05c)-3088?rJ9L&rnu-4)!E9eh5^v~Y-ZX`)&S}W1ky;rKXW#=zx~m4 zVMDA_--pqMoWOej1TD6)T*|h;7-XSevAIL@?SJRr3#WrJB&`Q@j5%R@S~`NZ-2$lu z49puYdcwDYAal2}9QB2x!j*HGmNrUgZuuRz6w zsCXWbS>KQx3}kA3bl%TXT?;3hoN@}*s)FAF-N?cKs&RhAp`0{nCkEEjsA*-*iA7^% zO=GSfpRv8M1&-?F{)*-}%xNG&!kN=1TH6>V0Lj`kau@;-_ez3}(ENFzT1G03t(<)i z$-Z31YhC7ewHgfO9x&A@F(*&iLHHm2sXMN*1Ra>;=ca8yO9y+@;Y!pJs6#dpqzIMh zu@duDqQ->X%&=WT#v`1-I0Lm|zgY%?>U_YKxA? zStz9^Y!*d-YIM9Bps(nc(ZLQQ4CZ=siEX5H5dVmmr;<|3fETK1>A~5&Y!ty}B7P}NKwpEqAY4C_RE=zNuB7z3g zq|y~!$an>ESFUWSyfB(Y&Ox$GW^&S|Zq@;PJPzdOmZ3{Z(ZJ{uZq^t+KPQAp1lhY- zVVsbJI4Fd)Y!D6%lez9F_I%XZ^6RDHp`A9?L3QtzN1}8vy zy{y!KeQiCMus>(j)8_oIg~jw@jHwwVm5~L_#MlyR>;=1jAbl^r#ZIlJvod;+%u)OF z$~i+n`s0;|*#us#9tg2z-uyD`w0M_3w#Y9MGO`M<9tg1I@?-#9a$KBzOnXfsesCUS z*`?q^k-%zfz8F9LI`4$(VpGa_N8*5C`hpF1H{=kY*Fc~Du_FfRznO7XWRQ*Rb52c< zrii%U!t4V}!egv=95&}8fnSj!vT#Ni9aupA8Ee1B@`EWSF#~FhKn6^nm`9?V6M?s$ zPRtT9wyn5NnA}cb(pF0)HY~b4X#FT{d(Zb?_3G<(- z_v_PtF4tS;60hB0%!%wk#DaG8^n-v!>QrA#t= z0Fu$T+GnU!`n>mWmhl;CfK7zA#;N;*8g2L}2M_6kMGHGG#9ydlLV6uRfEq*zVXE#@ z&J{>`Dj}9B=jWee+Cr=j;nS8kyuD^9VT7O4m>bVp148r-ee)KtEuPdE!U{kVq_wk> zgM_TVZBqrn3MEk_uGQEQOzShcits z&LuNo;SIqAsh1o-{gD9}hg(}@C^!I$X6fLGeuBRUPvZjpn+xig=e!j-;!&ycJBVN+ zt&64Z(f97oXOFt)E-3!4**FOVfE$?`83qC@FgIHFkTlpC#f?pSv*5B4&)Uo_YolPZ z8Bbb=p~|kpti`@XM{X+l%RzRtkc$kfV_b|8Y)Z_sbvV(wbJ$!;Wf7aHt9J7O>HZ)) zn`rLdCvWwi5O>~OIMyrx)7fDh9H~pC~Anek@J|(M&oke zXuBs11;$Uap*T?s{zF}7s`=Ml2DUE8iXt&o;C3Uz5>n18zIeak+rSBylLP3IuAV_k*v+y8mx6KPyJ zEi0-O_<1{A)(yHq1G)Q7eb|ubdjmE`P^QNP%leL2G0XUu5ia|B<@rHt2k*ib@75&MIiP!{kmK0STrB2b>$eAoK~O z34NYY_jVG=4Cp$k-7tbVZeq!t&at%RU!shyorIDlTX?^ z$v}Gx&)f&wI}5tuKzj>Jgz+^vTV`BA=-RJ+1G^XR{&6CYn6Ll4s|J%7=MW*ioLIV{Ge!$d~N6w^BIvsG|ds zo@dKEHf6kU2NjMa%$2iaQ{d7OQfv%EJCi*&EZYXhTkzxYoyguYzU~iuj4h+fkde5+ zr=AK7Md!G;jSdAjvlh%rHAQsanU1be zhgVgdT6aj|QLi`1-|q|?l0Duc?1HQ`es>9&;a|6G5Kb}8{lSp&NznR3Rq0=7d?HW7 zgm@q=9mM^7`-o^O>{7qNNsy(v!3u3)rlk+b>jeJfCYd4|!RLhh@sc_pg!TCqgb=&^ zt7Q))Sj*EC9x&?Y@JxY|&VNXwCh;toI1B&0iv*&g5?yu(5%bS=l$9BC9GW=D6`f3kny@|$GS7hvglzW5~kT4<$=?J0LrrX zB(Iyq)3NRrfPGF7ZNi#{xCxJ}5;r|on_;0-4H;2Ws&XrR?2wiN(y+pUoM$S3U5`Zk zbk&8LWKKAz2^9|!$2dxyfu+p=CK+*2r9|aiZ3rhE<=oo|{@vUuxtgUZ=BsRKN6S>*tTU=y-C6aR6WTdB{mXoNx}*By$~rf{bI zwIK|$!L&@8vD%u+jvTRbYfa_+YMH|VzPHw=xNP2=!DdWMEytwBltCtN3T+ci4 zaVlXB2Vr$@ijn!M8$1A3z}v05XR2?_rAlnb;9qIVI_52h4x_v3P?FeDzsnwozVg}zhvOYD_?$=P&#{cRe;jCJqt-&AFJSyfiGNQaYe}=hpHcdDc8NmjZio@8>)lk zO<~>6w|58}L)-`<>FSkbFtOvJ_S?C}ZR?T&U4|0khJ&H5E_FK6TNe5+;x-u_#r>#A z5~$`MQ8st>d)XCD5cLGl<9|S($%5_2-PrkJ{m;}u_w)WR`IbArLvsxSt?%`{&*QqE z$rb@YFF9MCIxbd!V-j4d$x(Q4}p>=RupKL-cd7 zV4GqgDuTT$ODh8be_qJd@r#|ZjTaN*cEjPMLGqKqcf zN&6hnwe?QTVbPBy8u}vd`iG=$7)e?Dynr|MrgLxvMecLHy@aKn5jyOX>r0ojG_5PB z+`O>8XY0hqk6FHti<#j=a)jte756432yCfpw#6K_7{2>yf#<{}f`A&InBgUYF2wNx zg@dIU%W#5&TRbLdJ7;*rbF+!wbfy{ zg&tGrc;vB;bPNBA`db;GjZyT5iU%H}@5y_i*U}-x8vF;4s!Pi_E-_HIA@pl@i7GF(8s~bJgwEE37H(WEi#7A zr?}HJcESNeq8d_%qAjgkh#h%It#PYaji&elXqy`IDPPbo+a+bhxqD$U;O%vPMDWcS zYnsvIer+0)t-65NPM@tT*62bex;~OsBG8W*ws0c2VG{niSYw}_op!ERnqjIB8rH^a zVggyS{`c@K?tcj7Vr@bt{*COjti2ZSS{=%9Wum;rM?r%SZ}AHr4(oWDXs@|1E@r<> znbno$sRw&$DvdA@v$0=%_-x%=^KdfOa z=PVoQadlcqzajj#nuP_iT^k!O7B)e5q|BnHth1;ktf*z(y*=UwP1ec%=>F&+P>v{@ z-BYV4y;%f`X_ja4d1WjvQfO(mx20Sdg*njmZa8q%g$;*9rQMqk8*Cd zA|dJnx|77`6m$y90wF}E&E%)g_{#yApHU9U?-60X4%4ANXoM;{87x9mi0PlYHWONc z9*RO{6LYWOb1Lyfm?EiZH?8A~#2a7k5>J?U4D!7oPf{D}|HGcg%KYE@DE_}akBgn> zf4k?^Xl*-haU%LZ8x%-fPBVx`E@|vK$z|$O<+N6mJ;(nGrvIbQsqLtcd|~_j1_n{` zS-%q7!}kO)6#*f910COg_m}bSKsepiroVwf&nrn}kR&U~B6oW)LSk}BC`_Yfqb^tp zqG0$RV;slXp*Ak_Q>-cOsUcwd!5n)mfBkO=`2BoCG2F8DT<9sydccBoHdJ&H@-gE9 zIb6#KCxQ&_mph3^xjh6*O(m32&+bCVWHpyr7sx{D@lxfA+Im%H?{nBjmeMrA$D-`^ zO7LYMB5CWCcuLE6a%k43Hu}_YRM<$0{vAGL#ov9kO8I=_sg7nsjVdf?pO`SL&-V%MY1IYM{Bn$ zrKtWlSmN}cJV$4@)-b=V+o6v_l07Ku)Jo7@$U8{@uYfs-}jqiBr8@x^PAa&W$L(3(cB98=j`Q z9rE!Y{Jsi@0{Lg?P=vSrPid!kzTDc(+I8H@q4vp<#n?-HbQ-mIev%pDBaE4+=S#0PnM`8e-qGc zaF=aAaV_j2`oT#4y~SPp_^}oP=UtSILxvpEFXP(PoU;CsAVKnAj}B=E57q9k<{v%w z=QP>l39;sQ z6bqQlMQtDUi1%eGR9-{?U0SOK4JZO5XO#R@qO%miL>H!3%BwCo>-abnPf0Y_o zOZ`v@$N&aa68ypQ-~zowWIy;{GZ#<0Lq1S)pzluF1O{(WjU~7VBLvQ%2?Ln0_``Fu zG1fakrnzMn;Xs*#hmy-cJSNG}APg!d=pmjtJb4-Q0T&_Nz72DsgEBC=_3nnRCxyFH z6mtiZjI)piU`UZ4fWI=VlnW|<`K*#)ed|=sFn!-Evx5dg`ru%4Z`=)olbEqVJwPYW zK>FnFVEVCSa>wLxdtd_QR%OYVG_zkP!ygpp4fxVHpG#R01jPJ_R&1sN$)?$J>GHc( zV%`*=l3z+{0RarA?n~+^ILyB8!Fd0ieGN8*NmF?xIb(c&7(zwYdA9xDS!TyZBfcOi zu95W6JhOH!m#nGowHaGZPmqq4`lA}};Nsy9NA>bAS!$LX98WAIuqCXl^Y$?i7v6y@ z)sz{ooY%zHGk+x+ldjT+xMJ5n`8z5Dq4k<%;&iE=aFs9m)p+{8;wS4$uOhOU;T5u8 z<6R6z0E=hAm3}UpnI^a8L+0$j&v4`a_Jak+%+LzYIq&U9AnhQ8r9DP1woQ% z{4McW2(!NlqQvO@#ym{9`vc%G9iF%8qKWk|fLk7V$;kG<^a{D=APHrXQJ>=w(1=Wh z>?t0)K)fLZm&ju_`71pPZJziq@A7*vdl)fC`ynioL zbM}Iy353y>`d^I#XJR=PBd${kLIlU+t%YMvqL*x;j=lDyYUh{7P|OzOJV7WZunM{k-tU%gWmf%8EMWZCWmo z4w~}KyPs*q2{8d>1`zeirkBiNFVNd{KHd0YZq|C%mAwSZaV~rqXt#-u?$T%1rf;Ai zyzVCKoLPkXx4ZT$SB-0S0;x1Ie<53c_qW}l%-lNaoe|x&fB*O&q9hy7{}3g4xLN=A zQBtQZixbmNs4qyOTfOEy_Ecx%Y@(RXjxDmxcYN}EADbWx77Hn@c%tqa6LokN)E10M zJT;`brSI1+#1kUnQ||6w!bx0L*Y!2zEdX7bwr9IY0z(wt3yfB29PkUv``0IzT&mcS zxlH}H)xG_*%=gz^SJ(G0iluR!$4}|uln1auWXgq23-@2UMoeJ?2K>Ll2t9~4D>5X4 zk&PM`?F#fjaPpvR}yTHqu0RgwI_4?9t8DfS>puZEzZQ_T)e1Eo=JiN>bE+eypNj0Dwpb(F03~DH&E0&e#MbXQg zJ8D{7ckx!AER6nUc{F79$-f8`g9%7pxF%W!&GF48q3^$kk^wdQA=QDJpG`KM6Lpo; zSM#qs?7|jQUE)di;*>;Li5y}jb4J4TNh=JrU{6^U7T=jdu|imu_mY1z;NEV3SZqWu z@r-?fiuAyV$19ofkUK>kOpD0Q#D?fIpfYZE9dkrg{^x85Dk7Z@qr3^T$nWZ4>{LlN zn&G-mK}<)E*!eqO?-0hP7^efmEb12O?v*fJB2=u;(c5&oxp-QZ-*bZ|;v0C(#ejWS z9O6na?=>v7<~cqVF`;`ZT^?zf>mkdQR$h znDxtj2%#jy=xB&U1#rt}9Y>{-w)4L@$WNF383#h%POf>XdBjHmk6AdT1dq;;kd~*2 z-;HYl;BOw$UmC~-i{GIzwt(*@pgjWeXrGENim*aoicgt9uzkdDQh#>P&E#^`7cHGZ z05vwG5hRZn^6XJQ8u9dwv#|f*MISnZe14-oECeC(mRR3Of()&n(6UsjkG&H1+|f#W z5%ivJEDf?b@EL?EQ-g$QREOvSg)xlv(6{VhimS!8DFBS?c>ix$7)s%E;46KRW6Crh z2ioE^>P~>g(J)(8Kru$@q+rG|y35~jParLOyxCr@v_EJH$7ep}$SVGX!84q`v~eIL z4GNCr2itgUo*y^5e z*4ro$SkksDipe12{9EsO#`>bUSr8}X`}-21xXT~C*Qgn%D*Cxpp-YXl++agIg|bY= z#epuH)IG3+q_@^pYyFCx%W@#2H6&kAlI}&St3N>%K!`>8S$zW>+R&C%rU7t`fTc#E_QA=dK0qE>{QRjIR$r{Akuf_R~@sd-i3 z>pPZ5zV`Rms$=0&>T{ST@d~8K(L5b42()(fIs8AkSc11FZLBBRYrXWF+=JzrVTP*Y z=cgl|!gxf=KgxZ>!itZT2#OX8+1uUJ2^352J&ex;*MDiqschC)#L25%ip!$oum_eE zl(5EK2VD1CY0fXe{)Y9^{F}8YVl*gZk^0qQBO&K&+ZQcj`q~|6V4{8;qYq%4(wv>Q zni)3U zxx=GqxymXa$*K&R%;+_Ynk%p7;K^TP(ep{j;5TSj@USXF%QnVJ0IZWbs_*t!Xis7D z(|6&_5&YNp0u?jNJpmv1)n>2tb_Ag2*Fn|Nje2F z97>m`R?d9)2X`q*Qir|MJwAI8T7;SEiT|3jYKmbua)3?UF3qD^V7P{+Xqr3_z{D^# z$D+>A&7PcEi*lsOEG2Un5Z@TBpb7qF(QXFiqh3e1&)EFEwg~>s7;T3>Rx)w2sh_{6 zPZZ5=4+pkr;$h=dG2R0`vK?>|Y8;$-Eu#3xrvX$(mr5}$cY7b+;-5oM%E@FGLrY%Y zCkPxmvKL80DpMefabnFm7>nvxx6EbUGviHP3LyiG+zO$DD?4sH!U%oe#nHeTAY(S_ zZqY+&y%fD)4XZ1M&9%g4NAJa@#o~lcuid;y^peg*2h*VJpDl;N{G)Ck*MjTQlR^Yw z(8ge<&dDx@;xEl|r3=N)@Ex39MbW=Gt3(PiZSay;JaF9NzBwGEcwXQ6)Xl)NAL^&f)s9ZVy35S6q4d;nwcyTOe#j;>ybyys@y7J zVZ5~)Ht7E__D(UPM9up5*tR{h$F^&aMS5z6=Mkzl;?P=cMOHqA8*V7;JEo z0xeF-6d8>e!6*;u)lG9u7Mi5h4Kp>Q6{j4=`v)+!r_vuWaMnz-Ee3)Sy$ea%0~N8O zF-7CW@80V)U2=z)|{IYL`ffJq1zBq2-zr7XA_5-ngot>^=HH2Yg3H4Mj5 zhZaQH2gjp>snr+Aq5IV(h4x^8FzLssQxRMfJ*Wf5o14+wa}Aj1VBIy~IS+HVG=0(XFu21y2J(84ad$*w81?WvvQd0`Sg z#{RIkL&vrlko{q~@$Y#sJSf<-tptVcc(iSc!Y%`A3}nIq>6k8@D;cQqGI4e*rlhQS3KX`TkL^#fxng3EJecg-du&;#A+A&*2}Ufh##55g>7A z0{OP>;$BGLOcXQ`^4EJjA`OWI_)DVy)jzpdi9}7*Z7(bx6c_qKDbiS8*np`G78OHA zuJ5#t*}jVb?tZVuOYe33huqQc`<7`$Dc9~u-C)=H zHN3G-rPq7XEBgq(`F$a~QjaT!?MS!pZMswjY#{fJ*IxCxo2SmE8;GIl>QfFhV7?bl zk6(c?TcgqT^NNey z-_IYlqjE2~-s=ug`G+-f__VBCrS;FP&ayr{0`1YAgi()z7!e!1S!(e36^3UAr zQ=U15ByRgpEx)?61fD3lRbmYMQoRw^+_;iVzf5MZ#^Zk&3QBhydcnP$_ z9gWwtBt(iV_RSCh;j1FY+24<|n@9H(-~FI?JMfR)FgJ{J6Vy7TxXZgai5Z8A>C}ZLFV1*k|QtXK`0{^8n;W zykB>Cq_IwZOBCl0^Z$EuZ^vbT> zDkx(c_@2Ugl&;P*?;EZ9TzIuLhE4XbF`>rz7)(RC?yo9R;@ocpUcMCldXwAj-ADjz`=O4}V}z zBH0;5ytejnuEm5*$R?u;0p}n)0@M5?LjGO1V)fBzy%qP`^2g{pRR{OFDnF zTn<&)te-;MH~!#cUM|fI3&oRjQ}cxq$?((j(eJa@u7BFwm~=y?FK0;NXd(S9sAaYC z1l!5s(W;@e{|>$k}Z=e`H%k->=OMTjlef zEv-}6vJ)iXZyPrL@KHf8xT(K)TjZ0|lgT?2roWF3&S<+rCsnEJFq6!;rVphsTAVUU zCR-*0-p-n?CMD-ft=c?q0@t7ATm`Q0>-81!+udru?tC9|=IraMoK(?N-36LEHAvJg z&c=f=BP$u5m3&xQ1U5cZqWvy2`iP&YI!@jlto+5_$fDyvQO>j&4R)A=>k)LQU`~X* zR2q0TBmR`9O(V3!1N1t|_50oZHTqkex}$F_KDN@dY}(-N7QJLEks^UQYq*ehXD(`R zw*Ya1w}lVHzWxoi{&ee?@f{RQ+NdTq(D+@GcWL=U#t$DJk50G#Wb-zMg!uH4-IJ9K zMdbU26xr@G{((6mogoHO{PI4(VN-@kA!t`%N;!{b@oo zo?SbCB}5lV00iwxO=fgaWV)2Evh+g=>E?!t&Q%OVtw^5IWN$RK zR!8I_SqC3L;6udQI2kbJZrPIGwr24thClVMbxhLK|I?C&#rn@X#D38w!E^Tl`~iOhDw6BlZg_pf=@( z6QhU&3VnV*Nia7;Om|0FnSFj$2S`ObOg8h%La1Vr3ams(bk~eR*_r$eGX3C`LevLT zh~ieUy|$6E1(%(0D?^`%s^GVvZWs&};fp~i>{|kM?y017Hm`A!W)`_7_9#eItbNwX z<;_wLwNMwnV3qx-@`_Jn`#5}oM~9O2(b|^q^15hu~pCgjrd4eCn5_F z83+@wTK<62iWhg$WqrrmR443P$^0?z;lx!lSfqW%LeNp=bn>pZ(yq*^eGH{$+1WR# zu2$%=tFXIMQm~ff)tpH=7Ry&6D!`Jv`0717vEgfP5i@ z@3TT&+UuZU4Ak7F6jA(yTQkZ~aJ|V~CX{4+#G~0BJLt;=jmA^sBl!(Svqna87a~uj zNbJUR7r-t;M3$>)L=&<=UQXP&m;OFvD1{qzQ@%S>(X z=I=F_=j1J)V9Yg z5^sn{nbS0rq+pMh%G6_SJ?fjJ%T5@zcWcB%jr7wjG7^u4(#3&+h_(lEGALv5@^d`t;DwigpQ`$Dw=qav1onWH#iHKmnveF zM?{}>7c&aDJ%{;cf(=BBz$$pN6u-;c0XNN3Hm~K7xLC?zSVIpEZNL{IE`7nn5=?cI zE>ZvN$Sz?m9E2#yHRv9YMjpe^D$2VjH{-WLKaXmslHLoYA3i*Vn&{|&Ik6%Rn&WV< z-FU7!1o=l~fIiV5EB)VjLpuW*A~RovA(-LWmJ8QxGi0zIh$?FJpSzH}>_<;%>!c<| zdu)CnLy+vV|F`a9U}gCKDX;%eS<$nw|6h+`b2K&L4*w$hzShl20YIr!aEmy`a7Vw2 z;}{zm@igFf4-|%3OT?6jhsby`dW_l0>BuCYuyD7e&2))(kGqsfH{B64>Vw&OXWb3qGJt!q$piy_?EmT23F}>5 z+Ah7m)NHGljrRzS1noEg3#p`h_ODZ!7r#+Bjj+9v8v%@i9`rj1TV2}$ zl0xx%74t4(f1a3}nVaqQiGqiO595wov*lOvzn}cWCrQJEN{=#-dRogt0Qs05SGp%s z&9PK@sYUZtP05g((`=1PmaCdGb>Z0;Yo8~`q;d)ez7hJQIrEY~VA}3+Qze-u-C~!u z7~&uRe2@S;sJ-ZB=9btHoc}p4_eFaB$lJ6} zOM(6nP&3iwcJorI%PPfzLw+kI4J3ymZFeCDipWbBo>VanIlGeQ-j9cRiQ25CV?MN@ z>?;G%EbsTBe{oZak=yY&S@WDh6(tqtdzzBc()syY=RJBgnI_x!GS_#RGJaF*jB9Ip zZ|lLX!rZNhS8sMG!EQkjlscL%bed}k|1GhaV;^}~M8Rr`A^a;Y8U18@F-+d%vZy25 z2zYtz-o4HR_maLOz;^=2?4olfN+`WGPN)S_B~=^dQ#QOw9@jl;6cn>&g;#CLHesci ztH$jGW_FDzy7#kJkSq>tLE(B>Ejs#WS3tp;OB$eWbajx>$xca3Rey=2u)9@JDY-np zPcjVawtcT-^J~x8?tygZ2xgZ?f>)H9$<@fId(#LQog_sls}f+EKj+|HN{iE%sC+bhCWx5?#rb9=$_ap}r&G59d_xi3ZCZMW(?LxbHQ?;Blo zVRI_&Jh$jDXHbGk5e{8{=s(Puh@lKndoG6qJW8d3QTbHbw$L|b>;+5|8~ymXh{@{F zLmfbm(1rqmLO`5WBo}D^QE1-4USg$+i+tcg;t4Mha|{La9&Ujd^YapmMnW(B28 zjMI42UVWvJevdr2bzERKlnpq68MY#Be$%><$6_!PE~qP<^Q!aA2~xz{qq2KW30|l3(l~pu=nlvN}r(CuxsU9k?X4 z90Hi;!Xbd;#V2bL)10y|qMup z6(T7>IW|V)0d7dO2AKdek|{y~6aC2y&W2-jkjq|8df>O1p34^MFgOsPQnYKsD6P<& z`hC3LQG)vcK3|WI)4CG}bN`W9?3O??tl9bz3cpPv{mV~q-teT;ZP@^c767ZDZQy} zuyoA}uL`f-JH3yi$c6fk*rvgx>+bneteX%>ovt|E6UXRi;^~+i4hr1>p3-6JQ_G_F zmRD(_B}O7576prDc#M;v^P|mLA?eAeCv7f71kQEofukr=p6;M;!y$CG{Y8$|g{eK? zkV_o2Gt?ESeH59DM8;ZexmwOvwP=JGNx7vnLIDMgS!_ZJ8yz><4Q`qsi1X>$lcy zh7#)^o;jU}haJRNC-9c4jayqSr=l1H!r7(^E~8KAWU?*lGMOLTl5YkqFx(EE5+u4a z+#@xKK!D1I=>7gX2%9rnpahpXY#DCmt`K<#ME#5Q3Q%lT6PLd*p(D~D5-RuiOowaA3|#(60ng^4B8_10Nm73VTtSXCMNvy zV2WLbNRS&>=vVB(%89d+w9S%5^lHVd6@uhq2ET1f-?6`jLXONbahq^yUz&Ro^6<)y z*RJ4A;Rcj)YZ_$7`15C_J^zu_Snf)A_&&L$ii-}7y%-@)AM8bC@;#sHSzw)x4GJ-S zf7f9}-ANa0{6!!wS^K6x@GSs4*2nxvy^}bAt4D&?wAaF=q@CSK$Ec7^@c5m(a!7GG zy<9^y;!%cs8gft`r00Qvx;7~htOpgx8fuP%t85DWRN02JfGRfgmevUgH|{D);owL* zp^;FB&{tH>Hb#hUHTZSK^^KJL*_R_z(jCPwmD@EjVl?z!Nh$MtkwA-TiuyhoXa zOHqF_M0efXqM=X#IA34J!22|^gR2_8!?@kkP_GwQTbo&(lrr5I1~*^o{ni~4Qi^f& z!R`GORXJT}(2iagsL(==5N%VL9VHM`(I!m?ONSG>0)d!1^m0POnIUJ&COt$6J}qQc z$XY$Pg%NtRg2}b&L?6ogj3i{uS)RshI;qF@X9?sIj*qSfDol$;d9p!Y!jq6( z4s8#y>6A{>IFu~))rG_ zt=aVsg5gB5{6;@mo>0{&3SvKOA`*@)%89uc2jE^SM5n-iVS_TBwa$59FKz=xv10rL`a&|-lv_G;~orAe{+$9q5Uble;AV$%dSRM zJ38uTZy%5QFSEF6BBlo$KNpXt2k*tWY!!X4(!Ib+T#~A{ z9O*dRzFM1E{bNrd?jXNr>MYXIBNyT2R~ll2&%%Rellze4CTPLPZp|n&-Rgi7ws%3N z_alu~vefydCr=wK(TSx~5(*7cu+G1|D8zja!K9YSSUR`LzwKjHnvC1SE(*0glkTlM z?W)0xKxXxOu!N>Z9*WQw33d)Ct_WDlyqS`0%G17ij@dA~FU`Ve7Jn_b3ydX@AHAt| zLC^~|Bx^p1^z2q*O;9YwMV)%>6g3}-)PQjLLEoB$m|%#<_4JVz!5mXfF#A|m$M`3V z2#ygBGglZ`ua2mM_O}dyqp(TnvJ|ckn8r%S+@%*$xbTcGTaT_nB|D`POc+2h$IvP9 zQK=REcGIkb9Bmm1F-O6k%dDZPG?9g7EQ5@LD}(c?i)w?fa5qUvuqg9Uu_mo_D|fdi z?AJ(S{@!~@1x!`LDZiTq%du1^@O zh`EzA4&H3|zp(4vBqaKJz#d*p;rw@|0vOTa(IKj(M47$^eeL8W^9OWj+Oiv#(B@K8gm-AF*f zXf}i7I}icZ18u6R+Akg<<$t>fdON9Z`vrdFAAfJ^>IEukvUgxtn6KLf^kP^GOf0bZ zdtYko&Lx7h@@nQji*_dNnnV8ec$NS?C(YibAs)!EGNz`Q7z#4UUwPHw=!0L#>&F^< zHO|}y(ss(soqX{#(~kc8VyG`$e=P!-Z z&*6E;)K~AVb<3N_P!Xkc_A9(AAjNjB_oMHe6~BiO_?kw z-YX)l?#bbf!o%az;a|MJzee}?@uEwoQ|RkYI3vA3WMg<3!q;!{m$BY(>WlAA{w;ff z*7)OmuZ^$uB%x}@^#0VA=rwR!Wai_qo0pRto!}Vce&$xpMf`W1fP0H zQN=zzvTEfJ%}C&OI=}4bAj@L7S$}iZAhY=*O>akWJ$r>Vdyv6+zY2~Z<<=c6pr(U277R=G(}uvvMRse_u&WR>Q&)uZ=IEG}Qn%^GO&Fw3XU%^GY|kM?L6kyztf zz$rsk-xB<|^ay}yW02MWF(~bkdqaQvO@d*-VX&0Kj-asW3SB&5s5?jX^4G%+p61<& zZ)tsg4f@%F%sbvK`D_YjdF|la1l5jgDGCcU>VSGoAW0b1&H6p_!$3euRF&LYOA z&%m$qwY0R!zUZldzY@OT5x>?j&M`Rl7+|zsRQ76TVd=5xG_R6#5vA7)wcLg!ZVOdL zRXD~Jdte;TZMxeLPC*a@&Iji^G(<;&@w}x*uWR<$%xg0hgLimm&oN0?Cjg=uGu0c$14L8EuMWx$vV<`$f` z;B&SrDz?pbKycv_a-*BznEzkH+2RU+K)>ef{KRRf+t^FV1R-P^6+)d!j8@9|L$_d% zAM>hW`{rcmpFfRQ>qX;MwVbP&3Au*)Yy3Xu%!`qgP}@|J>usmJ(CQbMvEm-uN2O3( z*62vx38!6K;}bn&W981-Z&1jv*|gqh&c!kW)SZldL05jnBR$6z<>O37ep8|5Y&h?AAg#>WdUpc!j3@{~?S=6LKr3IeyTOcNuU3+M* z+&QpXHA+`TVvq^*jaVHs?d8c@#-ibb31D%{rbzJ9z( z=>(-LkbuouqNBGx`4O0xd#O*Ew^7)zE!z6&;!wqApBWWp>pUhdgUGa5r0al%Jb3W) zSyV;Ipt?L(A;YSGxE@bn-j~TU)oj>jqcqc?88c&}EbKD5D`Z3KNtr&d>Ub-yXA!c2 ztnPL^)xJG+Pk+Xe_jJO;C_ac<`4hCNNX| zPqm(WV;7w`46~g9--s&1prya>g_5l7ig%SXreOO@utNcbve2vvImQr+DTPl3d0!*5 zrWrUS%u24?$PFs`+l#1XOo|>LM+g8fjP)7OK`N^f}=XaMTo90w^wpUTCiZg zgd0;58MX3du){C$&4P>y?gL^Iz|e`%(-)i-E&@f7JU+9sCAI`y>+` z23qa}D;!aDW+p1pnJp?$BIsuVXtKHOtj8%FSfa#VqFK}ply&2m(*2L&1v zK8@twQEXRMEVk1l!MjwI)-4l&Me4`AGT5n;{osgjM$gNnzYRgt%8u)fp*6_-3`)AJ zlKu0utqnzzXp(1hmi_&y@m$&Is*qxHJ6V|S{#>zXX#Vp7<(=d%r>N(CQ~moQc{T~l zFFl;h_%Vvt)=~l^cmO0_UK{beU54cDM#3!LzvsKeq+6n$%aDWiYG@!c`N*Tbl!U^D zSI@PhzSWAvR_vTkgd5hCe3|ksQK*rt77l+QTvD}>$Cc84cuSNjX;W$qqWVg5d=pY? zB6oB{-r?78L|K$%-3F~n3(~}307A3@;%XO2btqSv0;5@F1Xp&OyfDOb^MwxWatEIF zJ$9yE|LUsBYA*F@Za3Bc3&_Ro1NdKb2RrNk)*Xz@%&h-c-Epm><+R!PpVEc=x1t%D zsC{4YBKYY>E{BaQ?jIj}COpkx92sW^3Cf7msrOr*_;^yxNRn{066ZIB#Zm}H!GfgAo z;By$x+qfT>-~_{*^|-6j$Gw?%YxZdJuqU9o{W zb$@dp1~|8cnd9*$v)SBiN0E0(F*#XK)IYbPL4S^pYCnRM(;VtL8!s5gZBo_xPSCK- z^lUzs+PYgb6r-Ci>avE}K2wiEtTH}Si!L^@V8E8`Rwd@Yzm671=w6+?eHiLg2oVN6 zp<6;z2|-HxxF*Gc*%(~W5LGrqxRfUQr{@@hiN}RaGIorYXg=~QJW7Tm1h>-HVrFEP zUH9RdueDP*=&W_M13Y}aULPu(hc~P3yuRnBM|~ggXn{Y;u;U^h#|xPWj{WNU@9_5p zE93Aq)O7nd$Di|ew>43F({?A}Qh}+kPzmel!)M{3)kQmOQB23=|!Ae~9cRS6`w?@A|C!zdBi>Ls~m%#MvP0)3tHGSL}k_D2lCggmNfEATU z>R$umSY>7wj#}$6Jx%OJIo8_szEX|H>HEH#C;3^Mha6nh(DOYKG!ULGp(WU!h;)ad zh?W={cP}o8>P`CWGp05jXPnqFII8Uh^K_0=@Acun1uXeXZ8z{y1SZ`5+(84*$Q-I- zaEtJ%AIah79w!r`NV*zEmMMWntk{_20b($9=xkTchoS~h2Q~4PCH5Og_+d!PP(@k_ z{TR;SzZ`c%Rv8Ud{0=S-w37^f!=yTTgU67~dC9#2106bApMHZRcT1r|LT`I8_$Yu? z>6%vjG)cDUE~6scLqb^%kW6>lZyAG%pFVUO%45=Kvk6e5_5@>O;t?YFy)n>lAm8b^ zuUT1uZ}5oOOSq;c=-CCkhwOC&fTtq)c5?sM^6@V-^La@|5V-cu*G?O@rY z_qloo1%(5SZLZ$(sg>yGYO_$~Q<{zy;G>ZoEZ9F$)+M|-#LkWHi1*ickfoFlYQiun z9|yh*p+jCpK{^v&Jy4*>EOQHUFRqzxg=a3st%=JJdY;G^I$G2=VC0ZFm=mRW>k9B9 zo2Z0)sY@-5!4j%)yez2pgOPu@(x=fKIaoC~Q^#VX8x>PT-PTOx!AXNb$85y>KhK|{ zm+`70*5hT;fRzFde^-g$84n5#X|(%)e7Za{@`_&}x#H%xA3rMGE|L7!@Snqy_W3>l zZWQLDm;RBxT~r=NJ3+9n9SRl=L(+?3Z-t=G6g;8M<4_-?&}h@~*^opFTgcP@^Iznr zvSs5DfC59i3a^*Kz+^DcTk`-5naaIZQ+gbvAYY(InoMMdGN@qz(yla6+z*C4CgF%$ z^0qlv#FNovIEEWba+3+F3t$$3@|iP+K!=D+l{70$(olw6rAkJ)70;m;V320B;U5gl z>R-jQC%a<~Sp=f7Zw@h2h(#`>%B+XVM6hY}1`CbT$3>6nAR+4n%HmUHEzXnk;$g@(yCzTcZB z4~b5PU4Xm{B(>!&`9FctX0ba0e4vQ73})F)mM>ru6si8BeAcL@nK;Q6yayp`?fV(c00cbYguMa8s_S#(q{XSA|r~j^yN*%fc&4APwA5{d#C$HJ-JKIaF+*ZFO)~DaIwR`$;c=W?MD%woNnHdrm+pZNzICIE1ARAF|o?@0}ZB( zElCFxScNWOG>9MmQPb^5wZ+B!f}gzHrIS=^CuU8JFiK9gz2ot`Z=FW_ z)mN5Da3x7=`?g;5m{n_Y99hKVco{B0ba8~bw>lx`EYc!6+5)&Fx)*AhrjbR<$=rxv z25fuJvI)jfvs_PZT~)*y=$m;<2o}hY_WT&DM2F&gJol&2s5HS~Wb6@X<+;1ql3 zv`zrA)v|{v7#o04A~KeuJ)AzuhW(rw_A76!rDC84;WF@i5)WIZBb$zS4ieqP->h^V zv3@#6csuf3p}!B=CF+;ew!6}VJ?hci?^$F``GT&_oc6^A<~bLXXTy}8PMko`zE=NQ zMyi+4a$w+^&SJ?1L5cR0Xv=XciUV^^@vtE{?D#Y@re=SEF16fFg#kOX8y~A_zOMM*X<=ZCTEZ>7q2_ z_RZ2I34GQR9Q6QN7fMBZJ}%p$-z^7wK1pV>v8EO2BDfX_MD8ancs^f8?psn_-uun# zsTq#%H)LKgqLd;-M2NmGhMei78NTnsA1C*h>tQoS8L~Fl6C|@wYS={yUXR}^y;*Ox z$?+3C7oH9_$6kTgkAwB7KCyEW1B-u!^QbBbtixpWU}-RV`aJJD+b8Xw%&L_6f3`^~ z8hH+MOm(+AXWDvHNhu>%CZ;Yo3I=Z9X4Z9DIOkI23sw?Si;Iy(nC z%)x1-hF<73BY8xGC7`ZyH{=SH83%IWRbXMV>U>4tuUo(GgI_#il=?k6bvfcN;^B*- z!Ab?T3JvO|QmPC~cXet!z(h&`7GCF-g$9=}0j0^Q!U-QeN-jH%aPL3}SqR25E4Z^P z!r421G&33nsfPPby^Ogu>3!BkA@9<&bz2$C95ytO9AkleYw9K>{hSkVk=cPz+&*V# zuA|jy)a`OyQRu-UKoL9vtTD8I`MWzmNOUZREsT)PoIJs}MDSU6nb zVF5*O5`y8C(GDrDm?(((-8jjN+Jp14#*;-74j=lw*l}cb0KyW-Nw(ny>zUVeIB|iA z0)q)?Kzh;ngA1($?hz)&gN5B{&5j)_qB_9B5Csl;rlmzq zE~Jtv{SmYXc%@6kd+ciMZ7*svCz^0igON!ui>sTBC z1v3PnGed3ussr+~1dY;lL86k0#3Pn*n^q(1Ly z@oVf~YYPd!m_&4M9{}Iz?6Y&XG|}1!KQa{W+iYvoHg$rL!dwFA`o$?to!IP!*iIEv zW?6xYBi5qk15Sx+wiDV^q*WoASSqA4yY%Xost-7nuPxNxt=y{)N0d>@po>_X4OVnj z*R&e!L4n6BbZZ5>m#q zNe-Uk-hTom4lAOPWi3CUOrwU$WL!oTgBMZRuyENtqRg}_YQ$1hFARAGQJi|#US4&s z5s<|Zf-q+;o7~(%AmR6e=0Afm&08q&?Ut0s?n%&Cc1+~R*R*ZVvp}?^@Jus>16h~I z%c``@)Ri)&oXXy|WoRJ81zg>Q!^^n58ir=WN)BYo>_-a)A!3IEZaKXLbhL=Ct_WS( zA)3Q#pqGk<8NyAs77qLp%56O!wQZD_RPtR%S|_|(hAO@*#}}P=`r1UnQIxy_AG839 zdR}1Flq}q=P$zrH5{3D_!k|VN-!Bo%LV$N{bHd4ie$^&xU<3Cw zFi^EOfJrlrJ60#>s{{Yzq>{Y$x0+^}Qe|o+4X%>XqFr)=3(j7VKtsYYOX0p$X@13|$9-S{ZY!Th5Q=uHz4Gu41|5Msk)-?k z6ZqK#yv3pd>ymrcVDAfbvU>LitxSOhKLUZIPXrthC;~$DrZw4Lh(6mJB_*?Hc^0vN zx*LlVh{PxYPq$L=#B^_ptjqubv<&cHRwIT|^4OV@Q$U;Sn>U!ZR5ndg&qiliCV6+h z6rGn0Ml1}ca(5FQOh5ssp ze9BS_b32CirK~QM!&1*i@U=<%wZ47zfFwND)G_ zZa>Ad;Y#1FSH8hBOBgzJivv)z{TSnQkW-_%w<-w=<9ry%tiATO)S3I7*8dL8U3+U; zlm@3`G(kGI50r?T`P)?y6BJEGpTDrOl)o|r7VaYDI&J-yoHZp%TFSDQ<)R^}zyz3D z2~~Xk_pqg#?U_nmLy2h?t!+~Taw^QZ+tpvDL-X9`$|H1U7brM-yr)&T2cMbx?=cik zXze^FID1}7OM$m>j8Y-G25?fN7R6dOJ1pSHYQ|(Lb5x|ER|iO*<)5Nde=6+AEhA-5 z*0zyQ=N)pBy?r8F$#f4Rqa57&S|vY6WT}rHXZ??RM6m5KLtq__4-~*0v+^85#HGlC z0EZE1l!=%f74ZHpA_GY3>Z>k>Lut4bnys+GyHA;0UjVdZ2*v?n|GyDx*K|L{^^^gI z?z|8dKnN6Gd2>IW=Ta8dY03PmMvA%Ku3 z;qCyj<=$9^PYC!P@oIo9_txJJz0{C2Gk)NgOgi~G|bGikpfzjS*-~;Kc!gW1FL+K&$ z9kJ$hIKYF5h{Dv;qt}SsrcIDXAx~fw!Np!#GMlm>luo`&oQOfB`;+w2r3T43wz{!q zWps#`47`q2H+Q+-O z)aeDwT3(7IOZ~jth$je(HoFIJH##40@iQ@0#K1hhZ9xA~VS=_If&$s-&1s7mTzYC% z_^^$6oac~1;C`GuPiT<`5RgSy$;Juh*j)o&G352rMRby7jfS-Tbjgp(tFL% z(u&BBHJ-1AQ8w#?z%TF2r)uj3&((B?d7o~$Pg4Y^_~-QL6`kk&@PP6$i|bV$;kdtR ze>g#8-osu@4K${bR$TE5`nF#%L8;YWTdn|8q3>2dpjH#Dn3`TtK8!Wc6wHFBU{W3Q zjkTyccAL!|P@9E51wx+s3@E^bQ?rGRes0W&jTuhSyyV z#c45oP|Q?@?C8Z#It9Eo=nNDR<9B3k>v9D$EI!=N>vLWD=Gza?RK9D4mRRI!QH?OcldSb_#_Cg!htBd zXzeigW^@^iXsqOsWuE)o+0H+a0HR>yt`wLy0ClkUY2KBO1>6r02PQLdECgeTGcsLf zZWN$7G^R`fXD%OYT1*os5lj6UWiIzXu|p#%vzAY43hZwCb`eYXuD64fUDdbd`E9;L z*t-3Q`6&CP%Q4t$9E}rqbw$Vx9s!sg+kbB--emeWhUQ}}(Y_7tBRtYwrozCh`z)kO zP6(0nRm}tbB;eI*Zb!qB+s_+UFAX(#^tvW_BrtZ+KMqKlH#;Y?m28O7LeSZ#AnMcK z{*K%fGUEnXyGqws;yaOYpce;034hfBvP0@P2ve_!-Ju$!$ z^BR(G`A0P^138F?^m_Z+x@@k}NRP?Hjf(_`DLU3NRG1Lj&|#@9J}!)(Bp497gInPb zNwmhn<9tj!0pcRTGkn?C{gky?bS$j69wXr&pyH+6i|||IJ3jWHQ@j5yG8yUr3#*Nh ziRJ&yYMa*3u-gU%fZQ%hsPK+C(zI!@_f5jQalP zLcA=KKy)tdXgC!#%^Rt*tfGf2ld&Pn)WU{d>iy$uFtE5pJ^dI%-`LXM3cCp1%-+}j zZEgv9^<9_|iX5^Lls}cQ!AwNAz<9kq(nl*j6z4w}EHm`d421!&R=cbH7g)FNLTF}NLW z4o<~iwN1;jxrp3Lb?IzyKFyD1en0Re-SBczYKJ=uth$eRf~mFExHna%@#c;lO@rG% z_rzaq^%g5u>g)2IBvZy6^4jk@4OOkS+U?=Mm}i7QY|>&ezn1&!9dEAcv5rQp>^&;f zIqNaTO|#HFy<6%emM!`{9*VZcGofMJ#Ct-!{;lo?0a>e6ck{|A#^xBdz;mqT*}F6SYnhRL^;dHh^Slch%S z<7t)Z$14knQPrl2CoZfC-Na$qV_sQWCG*y(Ig-srWl63@ef1glkEY##+N@g3B@M8f zv~%T32kpQKBfQ(lhCf|ACD0c{BplQ*ZRiSiQPP?&R_lE zwoB?5J;mDya%Gz{j~bV{w2>F>mL@Z%i{~h_NAn5vQ&U7rRh<&S;omz5N4d~#j1FA@ z1gJidj0YNbar&QI#1gqw_JU@s;CL2169a4o_JM-M18+255^*v6yP8pqb_EEOx$GC3 z({-#TwBGBYV-%`Uwk-mZ#qU;`!)rqe@PZvvnark8Gd*)SM8(DObw?~?lkR$%?;p#- zy~GY&tk(0~!n3u#R;vVs$XE88CZ-9QY-aZQ`;;3NkkFj@(6)ozvbeC=0 z*vqzU+qP}(W!tuG+qP}ns(o|MsZ>&bQs-^Vx6#K;X4)LR_4bi9GyZ=^hu$z#=Q{KX zCX8K+opPH)@O)PIeOpa47J3#(zkqegC*^XrU2M-e_px@qx|(>|Y$6LEna-yr;Y-_sdV2 zGnU;MY;<7=ZC1b;9^Y!b9I|!7$pdR^@d>8CDOC>s{&YdP-Wp!<&c_bL_&{71g&CVE z8<1wA!9%zL@)i)wlfHsYxOpaFG2sQriAo32p5mSH@NN;S_Ds{iaEy1U$*S~+tCW7> zGhRgWVH!af1%@w8R$eXxDxA#mW(-L@b!7|%R`C0BKVBnV{;+S}C>PEB7`(Dy{TbuB zBjl34RO?C{ecn569k0(!Vi<2oWnsY#H>fsGs4b~$B;w!V{5#o?^V9P{0ysq5Z!cWSId z$~1lVSp2zn=ib4e*^%^vvuzM#K}zy7$|n8MaM$dFVg33&ReLuKD!}J2BEV)q@IHjy zX~m$%Wo1eh1lFEM0%ZCLjlw5+I#l*4#S9fB4gtMfVP=c;=W>}uk&dPgEMFj|{Vem4 zp*dgrn>+oX4W!Bh-=lCuM)X^*=T5YGvBY=}3M)Fil?~h0W|xC!s|k>Rfx@5VVD%q{JEaYqlFcs;4;PQQA`|G zRw!7ap6aKPdmL11nc`Kph#8YBm7+Q(;A?z!OuKaR*L3GCiLMqT166Bq~DNJ%g0*_ z<=M28E9PMGLPZ~U@ zkU{FndI*CI9~Y{iP57P0H%}m$I9P|irm#_)J%2kl8FA_=Fj9;W>xaUsrGI=A4Gm51 z&<$1dSt(~Id9X-1B}b^hDc@Foq&vl3fnjprNG3rpNv4)M^AuG~PjvgV(Hvzt7 z%2MY|4R~AU4na4`=A)+)j%%cUJcd>nb$ddV^~a@Mh!BTwd_%L7INwvGP~zZz7dOt# zp}m!a zd|1`mYZT)%Es+v`Ek8D04s2Z?EU-Es&d@>b{q=I!VV%yuPIdS=&o)nDRCil6pgHyw zAhBwc$%V?a*edn^?lyNk2qsER3TSH1!&GR@M*fCYrN81a;TXCTFb{HpBHrWf{=A%y zuiIa@$+C0!vSh^6{__0TrNX|rA$0vInKC~m;7LkOvjYJ{&acQQ$xL9SG1;EcU$d`c zM*KVVPUb}5UfODwgE{*ys?GqnqQp3pHz zrjgPt8bnJkYFeX-878XK;t)u=jMr2(A-pVV1}BKnCnIER;M~LTOiE{w{hTxuH(Vnm zE2peT3*c{IwF)|-@{)>IHblS#{CNe%x56T)$MW_G$3vqgV#)nJwLQ016EP$$*e>z< zH*|-}=VIL5^IsA$mEz=v|sez-t#+KVW(_)Hgd=J*ohaJ~0W= z9*hkl9JEj3gz!p7RF*wj$I7A@OQWkPncOU`F$&c~;FOcgk?rgZ+GGRghqX=z{{9Po zyC3^dgtmk`EZ9Jg8ZLmPlAHbBtK;b?!{OGq-%$+LRQPnHpOz|z3eygi{9XmA1*v}`W~`=Z3G`;2Ac}S;`8*C@bP}!1;0sO+nt`` zif+N*i$?rUYERukTc45=zsCRzmtaC@qEy<^QIiTiOghK&`QiRzYl2q%6w3VDw;Wr@ ztL9<))^)VOphA2a8f(u@Rq&oY&p=7lWTN$KJkSNe+Ec^A@iW^1gHzGjrEQ%-i4LKw zTLo#Ux8|0cb@JVyM?sbc!4KJhZuW=5Sp98JwQsYJ(Z z=dT#aJgLPX+doRy)C-o|kNk_-Wgh9JlE|eW;RO^H%t@^_kqmdvTGcA3axDk~m36Ub zYhtTfLzS^^*Cb331wI=f^xiQX0UUevFK7awdZeu0>}|!W^`$KOLmo0 zTO`|)fm5N3vkO)IaBGn;@A;@tS)!4Q({-`3&VCXJY!Lw~oRWYOEntqXCb%6jtFwpv@cS@H2uqyn;bEM0fndA{-~P{%kMM)R0K$EN z5+t3Z0k}U|El|$<&8rox3h%hLc@+~i`dDw(9!N+WGTLhP|E5E|=K9;wsM*Qt_RP_# z*;4@n-VtNc2`g-MLt7j8V#zap{?W7P^L(iNq2bqOdNpR|Do?~Hg zd3)2N#DD<{7VC{5G&(f8D3eVgKkigyPh9O8+0gWkjeWRT}B`)e}9$07|*yxy9`fcJ{!@q_+!{}+KUvi*U?y3ZB`prpnPmuk`9P639T`h-P0c44w8On4SbWX) zYl(2K7tL(KUt{ju>Uyw;cKn`?FHQ}vj909(c<(O^I5Pcu{lV5+&jFVBe8af5w0LX9lg%Ir3Fwd5T__P zKO@2ATO$#$uUEfng}$meJ2Dj{zm+ZP?&0R*@VRV&G$L`<;pOPeX~Vl0WVmma3Q9_B zG)vKTLf*PP8V z{8hG#)~hX&x*Z2^Xxi^*E;rjJV4^K+s`#h+Cbo{&4`!VI=%?rOS7UQ(=&~>_c$ykF zO(j0DNw!NuJRK66i)L&;(Ur$_cJy=MK0*1U|Gj%)7N8k!nbM9yhU)rbvzUC1G+THjN-Fc~C79sG*;g?9T<63; z&#EShdskyFuQja_c|0AZc~NQL^l)-@v`Y>{B0yf%BAr7O1~Vwp#m&*1mk6>GP!$Fd zI9`3zo;5~KfR||Bo94~=u9hi^;Ft%9PwyHAmTiv&W{~>#5=k^rfrJ{Fhy;klGY+UQ z50C{?l3hAttnZ7eFRWFIS2GdL^^d>H14&Jy7~}&L$9)wKA5Zp-_FP9pm|XNga%r7& zt_o7Ocme7kQi{i5-GB|HCbfc;{yj|R;6C8C1_8l1=Z5#v;5p0k;JsiSLI~M3d@Kt4 z2~DLgc!RgA_ZhLCH0z)9SWO!IU7Kgn6n}Vw`y^s>S z%3+ug%eXQX-IC?W&Q(|vtwGtD^ha!$^ONWI;Pbn;L@{4h-7^8ER%P1e0KjHe){}Mp z7#dtb2IbZ!zySMGz2sp*orK~^JRIH1IUWcyosz-1=LwL z0xQJI&R=A2BxV`bfKJ2Ir+PgBjqS;tb?!2Q;mb;=?5bmX< z&87@;5$DceZUEgC_>SBenE_?noGlW{U;Q$dxr-3I`|iU0tAG!3D1aMz8pt(`;IkeZozFMgIU=nsL2g{KXg&o!m9Cnl%V}BEyw+3g_r-T9 zi`Znop2ns6VQRs3KQTWJ9DALqk2DK*EYoj)_W{Ed=-5aNm@lTjLkDJEUP}TKt;0_h zgFDWbG9dB+RHK0gBk{C1Mn(M^93;J(Mf1!xcbUoLAV=G!>%xe~V?`4QT-Vjpi0)-a z@iT2Cf$F(mZq>6)>jU36uza(EC1cyC^5D9X%sO#Sv6C8}ZUci1nfX ziG2cRUk#Gd5v)m>fao4QjBaWkhV2qen+3C#AFf=VkzXe#9Xo0=h*;ggJ+H=gZVi*c zj7oo$(JDC!u}3}Pi`HSq!xf+w&*+}+B>r5bTm)7Wz$?6e`i8!sX+yvn`{-NhKy-{W zHDnT!>5L#$Bn8vjN^zevd0m|OfU4iPI9_Gc`&Xi=!pZF|Z@a|f-Hqm!0xnzls;`uI z=c`et*+UM<0K&9_(%Yd?>~)SW8_f?2POpGZ80t$nt}$n!DyZrnT2!=cX>Bx&9d?8q zLz^BH5=H-Gi}c9DQz`@AaopCQ4LS)3RC@TG|Kk8Bj?U)3;sDK`n3poUiMiAX50_#^cLR4?i+x0&xwW~qmji@XS^Celqji+T z8=k{Dhf6ZUlzqM>T~0uH$0D!FFc5fmZ#mP~HrMxspuEINKy?&OTIUG_vg%PgGBdTR z^A+-`7o{_Xl!}quGPzfGwp=h+N5iC5HLS0p*6Hzcmb4ZBYc@2fV0T`Wo)+1;LOVHU z5}Fi0lUDaOh_Duv69N*0F=K;XZ)*)8CB%6YzN8F1hR+5v)Y!s6PJ(Fr{EDLz0@2m{ zwviGBn$7*=cs`w6vfl#`3LS*xz(RbDt=Cj`N+yqn?eWMi=}%h!#bZfvB3OKl@5Q^R zHF}S(Q8aY-1tkM_d$`^8_6{y3l%%x!#K*Z6a(vnRq;5kdh1}ThF+WFOT;{$m?8jq= zU;CncbWznIg8llO{4t-iM{$DIIGM@7N9o zPyO(}CqQDsHZoUdOfj=!o(l8QVD}$8Z z3#HiNnjZ(2Ro4l5$rCP(kt~-9ve2V%GI_ha2-&$Z(c`C*fJRk|1m>eV=mE%Nq)i#} zk<_+RrndNZ{z2GCcKSHS`yP@v4j9XJ#}w0OW$Q)ydmATsTns8!8C6!HIo&%xo6UAO zES2B2yv+XaEWC?z7f!z5!0Lm;E6H6jfH3{_B)5)4!K(@0lcWLuRcZz3Gy=Dpj?#|L zxP4LWJ}uqO7-6^dsiCA?>eM>pj?}Z?BM0`Pf@n(#HaL@mi~ZV+2Trxga6d6ftY~l@ zcuRgTSB?H4bzWH-`4&HQkG$N@KwA*`HX8=C2P9Y62-=rZD0Jf|2BftaT(bYUs}@?c4~g}nL)2`{U`bOAFiJm z82`&^`ai(bF|jN%snE*<7zu$A3k9PH)dRjiS@tJz8MJ^ zSy(uj{`>KtHTb`m1dOZ_kr@5 z^FP=iF*E%&a)B)VLx3)mGvvkh0wcpQwDHH*L0BOB}MjqOPO?8xnq#Hrv^qeiLDt>z&`#+?oJV ze>GV}f19Izt-jeVj_&az@nXUu^(`$9fat+8v^M$~zNFYFxR|^EKK=BS)Ixt@zlFx$ z@qGa%{FsY?(txIa#$B81yfm>XEy+lM=qMkLv`}_5PI~ZDENIZHwu08CTbWRV>QUpPt~UjCZ9z z6iU{QV4%J|3l(0E&`bBLOUzp+_dzkumZ{dZ?(q zNr(93?_qQp5)xWNZ!>)w0ZzMZdUQvRD(83VOlK?B88;(5e|#=H8CBTL_l`V^33|NM z&s&ef@)#qz^9Lr+fZ8ZPw*prrVANIvOSZa39xCTs_4W?NPr4o`GcWw86Lq4tw$gbU zRC?=XNvn2m@iyt&CselU&RMZmETKI#hU((M>W62U8vX-;0q22UG`);(9HK3$p&l)34;R;NL$io^ze z=}TcmGM{Js5yM4%t8Xjk+<`hO1L2V?($Js{qHJkz=z;L;3imhd;TB$E`9xT zTUhNKhIQ8PG2S%%uZffmEg#4ztol!?z{JH$$SnagH}2~49^g0v1O~p>6_?}D2dZBwb+{i|wf4VYz<^vjjq(4nVX#6C% zDHj~Q@Fr~#9cCvyd!NnfbwfI4XbFlSH}|x%Uwi?m*nS9>5o`};3B>wTK-UahV~`$G zt6F}p;{JN2GXh!fNlEz_QnQVwVpV9{muzKTu=cjMUH9@Z*bf zXk9Reihmd6?W6&B+Wio)hFEpnSMq`bFdj z)z0*+0M1~n{mFjn<%}tZ)Sic!vk38irGs>h!c24dQ7;gbE6rnDCWVUMxZ5wz zWvxIh)(rv1r`v~T+S$k;^@+oqYj`k#G$nYm=4!54Oom6G3X<08bElyF>r2&U>%B>w zQr~MVs`TG4FePH9g^dXkJ7i3p0eVB{D7T2nX|k;Vp{lj)7g3<6e@XOcT|6~N z&%*;m-#ke1N%JjHRlyCwpiu%(Ytmi8L^hteX0|HZ%_;femj7f;BJ7&n=I9M>!g=tX zEu9^R<7`C0b*2U!K=rbDybQF8#_O#HV}bGO6IU;;+~OxErYCI;VcDWoej+R|m0k7> ztD<|n`J*dw*BjErf-YT4@nYd!DKjZGp0-C5+Ez*P!3WTer^GTV;&6@~?x-;{w#oWMXwY4JRiJF0vYxc{}=)gkt>Flx&RA6tM~| zJgJjebF}^OoF$$lgZ^!rXstd^R}%}lDGSXB3|h0v3TEvAIX{xCWWltxkLsgYiLo9Z z+Yr}jMItdF2S#jM%@ytT0eka%Xas;JBg#KdAH+I7Tpm$e25s}cgLc`N3|EH3etl}K zQy}LawMl?nC9keKjB$QUfI}j?_VQ~@jUCd{5$36F2uH0jT#)w82$|j7t z35cD|&sVWe@elL)s}l>%f;lOt8mX_mbg`tfb|=6iZG&(bhzLkNGvnS;j5t9&QW)lU zM)k>5!p>SER@^%ZP)xpvYh<*RITg{eV5ztkFZjIIP{Z47E?mUJM7sIKF%t+EC8B~! zNu(mrt#pF@F~znuPU1OiN|R~mE! z_nlu!;kK$?CmlbVYrT zdNW_jMtI2!R|=%6V-r}~R&}##G-vpLcF|%=HTC%w@Nl=Jz*S;#Y%Ls#6RnIKL}#RL zR)5zSfw-XjDp5~G;{6-8+kW2qzf_BTNx6*9GTTyk;;2(cChM*jow62L;Qew2V*L(itUA0L!_u+>H#|Kz8rrVaj)3plSpLj1A2(T?hw zcK$XrfRPZJJZr91!wrmhxpv=hYDefI5O|II?l@X4JR5wAk<>=`)B~aM!p5R9;AVo^ z?A89}Agf&AdSmJ(OFOtM+vKOtT_vX_7SM~g>FJVG;*%=qyA-XArF15cD^KS>qsM1Z z4?0T9rW<>=IoRY6OQo@M23D?Mm1+@aaYB!(u5fk|d(*EC4~$BY99DbzG)FW@P4_{nQco zf-el1Z)c(R(u;G*2IOaBTI1$XyaIG~06uDiF9wWhPIKsFjssBj1`!Y3V1BZ+rV7Xg z_R4(WW2$2IK3hN(kT(u^{|;81h9JQ>ecBr+QE(9*u8WN`FL=ps_5{rDeb1Xsd^#nU zX{C>NHed0)44S5ZeAtfqfb~#Fy1riq+D=NPvT=#{A&1;{On0JHFI)e`(L00L!yeaj z)u{Qe&(8Tv@AlC}I(TQq7~?M+=V6$SDJ(@)@%D4a*al%Up_4)YiG~UIu2sHY3 zRru%JZSP_quML~=DROF#4EWj-q-^YEf4;@MSpB`}2w1~JFAbHw$1+J31H zF0D>%1A;eQY?hegqafCAx+wS5{y=tUns~_L1=_zzM-?A#Vgkbs9y8|%H!2?mI|O;j z`Srwv`W-Omqs!e^D|wQiQO&EWLrj?UtmZz8|B>hz-paE?&i8{M0oycQfE?k6TrCQX z=tXjicSRxY+zkfbjk2>GH+66hzV7}+au6G}KGiz}TBzVoJIAu~Z-&1**R zH%ekOTE#dF=mv<`L~-ImyN;<%fYmSQ8wL!|`|~Fr=V8ukEaBa5G-Z+apRegODo5)4aDJj!ojKfR$UCI$ z5v^yGz3WM$fCm_Rk3-93qh@cO@3WR4cX6xd;Z3jFc1AJPV83x*XSJe81tCbXCLEDy zzCsXr^uyA0FOqCO{{ry?>6YKyt#N2x->0dr^6 zmBNkVoH)su>DF^F;2V2W9_0BDD+IM)YG(W&B@H->h_JUXOMEGTQ;prNTB7}%u{vvM z)pkve=+Z4sN9aIP8=j|5h;uyXDfzWZTlZWSr-w$~!w$B@FNunlqs& zQNjg`?*PaUiuZlnr5wtQ)GW>p$Ppxt&N#6ciI!IQbDfnRs^$l9jF-G@np-kZtO$rC z!&W?V?m1YV5ueV#nZdf4XNo>WndhSSVYcYL$13lmEPH&Z5D;^R58VmpY>J3O+urC-Wd)OPGz*ubDQ3m zy#rRRlYACLjwy`C!CLK&K!m&P-+jSJ>FlP@y1OfVVRlT^xR~X^%gyi3wC|Di?&{8x zTj#zV*$-{n#hPYu9UYP%BBpEtcB!Y*036@iPqTi-Ih5)~UEJbiAd@BZw zii)fJXJgF)jVFVqwfbCM|5!u+<{uC<2XXiCQK!c4<3Py}9kuya|75iTyY5DaX~c&T zXkI7O9d^P+@Fu#y>DN)Ygnpt+>Gfo)j|Hhrk>)O&k+Cc{A_AK3j>MN!Y~^<)0F<1` znwzA&5THha8RZo#hy&+R(S5Pbv~Qh85(4d_p=>W1L|k_i=Q;zw5N94(C_f-O<*F4B z9?if1{-&CWTP2zvB^D_*oLKX$PfrXY=T7+E+=O_qv&T7d(ls_4Ei~eN7evYh8 z&vvxtgs&G=VcRQze#kn0CkBVR>WyCCYCTF4C^ALhY6wky6~GiMW2j!8CCrX zStq=FSe16&*4QXJ%hWkii)`X`aafvxF3l5%$t%X?yLbp0A*F=y#2$&PJk)~00tmL? zDG2|_`QF)|WQ_UbPoT2&U5I{*}s~2TGrV--34XMI~BkEFj}SL$SeJ zWg%<425?tJ>tOPb9u36C^(ouOQ)T(yknY(zic$)TFQ`pim)VyJd2iu>g|h{$w{1w! zhy(fu*B-69`KHCh0EDuSP0qcZXW(=bi1Eesr=KwsCD;7^m;Q4scYMI&s%jwt6LfbY zQ@7RHN~0aT)fegXTfPiQE550aA>KK(N9Pw|Le;IoklQxwU_VC+sl)RJ;h zt=l%t_pL({1MZ(pgFH_3ljDf=5XDZ%zvm({C8a6m*oX9@5bM04C36J03O7c%sL_h6 z-y_;NvKB8cDL8o2r^L(E39=jNcIe8F=8WYU`3acY)u}}jAw&L`koZmZI1x1=g4YyR z*axs6Wa+r^GdM2_xLmVT7WdIXC6&YwaEe>g#k zCgmOh)kUG~v731xAs(c81WqZF`o3hfpx%3biqV7hRYSG^Fnmt>rE*AOhwWtWhP0sj z1*=&*PhngvYXNPDuy^cuXIXUyZz^GE0?2C_JqR8WNS7vHs9aYD{s7j^t@ah;EG5HN z$Hcmadu#c4$g-S;!fgM>j@ho{kzW7sS}1r!sor=OO|VY)`t7%)lm1DxWoflDdGV1i zSUdcyncq0twHp+s89VQGgvv5ayD9T9mtH3mY6xno?>dQ)o^{?r<KjIhC4aX8fAPG8xFX-4({qT_dkrud&KqVGUx#`!^d)m!Yu$)m%e`m2}MmBNUR9{3A{o9AVjJHx` zdu}EoDNWEyF~28qv2ar?{#b_+#(xlPG~yC3PqgxaGFvh&g+xpSJhX%^;&Xfy!MQ?G zN2u$uiTx(yd7lA_CkRrMKh}*H#}BLl-~bV+apS7)FIG`*02$JIQS6!(>$*N6SqRiI z^gzh{bXC>}z{})&%(yyS0j4giQsdWRC2O4@gMMpy%$FMxp)W{RBOuim@a;&}WCZ+9 znTZ6$>E7CT(iINh3srcnYsx|m5UrR0m_?(MMy&bRJ}t}e<_^t3x=e)$$<_4t z<9FJd^0ScU{%h@t3a#KIRR7#oC6;-**0cuIP+g!19g^>is4uG{}3|HwDyHf-q)O-QIEVSM8$GM`fdD z5*{9+iTXK~ETRM=YN1ef;dXmMy0q~^1v)`9+GD~jB*a-K7`Sn}AH|EwA?tpE6An{% zw^I=utAaFJ(=A4+KhOb#b7POw*O+_Nb>p4~&-cQryfAewp&_AxP%1+)UP;Y`cX)!aPhj^W=QYv(Ni-gJ=I!42Q-uSEgH!JXEe@? zvv(nv`0jjzn$Vz+<$IA&Ah6?HoZ|T^UI#;ylXfz+WHlt@yETUd z3gqh+QF|6n>#$t^^e?40^nRz8A8eJpnu+yySoy>aA>xQrGqmA-Vt9NDL~s7gZ(wyF zGlR(ge7s}w`uX`)i^Y`hv%9`I?&1h)Z9EhIFkh$#+Hm1p-Z^O|Q{ftbLdwB)73xoQ zBcqoC*oxE(jSJAKc(uQ=>CbP3k5PWmg4@wL1y>$$fR^C%pD^xRLJS=s-3BXAxq#1^ zLbI$LyUOk9ae4u(Kt1ttq%2~agZ7SFSLDBUa*7Bh!KX~3UMKm_gb#nvcV#)R4dft@ zhlu@kvvE1;NyepNXChZ98<)8?K_tS_L*QFIBS^7(lFT~YSmjGhlIg>6J%0--zp|1l z4vApZw#{>RzB5u5B(kT2g*ud%gkj{p3NUHA2&L3RLq&0tqLb-m5$dApH@w=Y z#$JC~h*6?j|ElQtYHnVT!orB-n&RIuuZ>m^X5@qz*{l|{H7H`rmy|C_OC3)HHzt8H9JF4T{$kKEj1!7qT29f)qAp~N39N`VS1iG3ekXy z$M(X*jaba4|8^ch<+s|MLgYMmdo%cq|Cq1f6go8aO~q*aVp|U|kls+yEf$oT!!oZ% z@(+b{d)uM2m}?j!+y0Sa=%aAQEa%+O=u>*k)Zi0X^y9%k;lzPPPt{6;Lyk52v*|L+ zku+=kz*baUZH!IV6ztf?JP-CFkNczxwXhzTF!&sJBW?~+nN*S z&gI|46Z6X9c6<$!>qRCD6(b5lGa2@Twc^({h@a5uja(;zX)us?h-d_wp9*fcoYkBhSSokq9JdJ%0=gy5V0VOL;!iz%Q z4wdxLbZ>;P$A5|Y5oTprJF0pRU>WVjQoM}_)A==gTE_1}3>>Jme9O& zcvY@iy&|C0bR+Z~X8;ak5tN!w)usP9 zN?g3D7=|1py6t^Hra4eizC+Z#6)ffc=%0LAkuv?mm)z2xA#-BspAD0Bcb0kz4csvy zn{!fuNOY_Ym2Ew5BF5#m+O#t5u~Qq0fs?8sb$nnZ@UV*u`EZLUm;Em5Eixujvde_i zqRDXY7yjD}YM1x*qS&-YVMQ{1plOGSR7G!-6m&OU%a}LuN{rT7EK+F*+7Q;7qyIP@ zZ(vLDSyt15_%L|0Mb4Io7L(r~@0BMQ!9g{H)mkif6bXZ?gWXd@m6g+J9Xad zM~Wi4oLnOG_ol_x2lGS!SuKU!tayY8eT{|iqgBVM+{vl>jr~c2~v25M^8Is z%R;T>G9g(Adchwo&u5l=esmHx2map2j2M?w6|19dEdp+UidCAh2WyXv($0T^5p2v8 zlM|6ETDHogEACfCUh&j7dPx?=BDsemYCmAu+;$WtKgpZXWqjZcua1|J339NlEd=3g z6W~%dCvR%uDSxNq{SCj)lHU;^p4vLcJ&{RD2jMhtQrn_^f`Eu=UBCN{x1AWSFIcr- zXa`q&UdEV{KymOc)?!$u_V}ZBKqoqzzM-=5pkXKZcN z>qpbp2~kfG3_3wDAH6adsnZ|S12{X2cG+(Lxj2{|alT>%1>ragOSTGd2AcO%2P9_l zSOPg^J>t>J4=O{^R9tu?9wZo&_wQ;WxN%zp-5lKnsv3G!FbtU=Sj4zQg~>b>q!Syx z2p~ymgSE)!V%CLc<~Y9}Mv2oym1zp2ymkUrxkG9V;&U0ps_tJaAJQFiTkZLn0d8|< z2Oc_kVJ7sjgVkliHq{d?eF@o_CuY5B1h`LspKejwjU9gYT7%ld_)}{~l7NVdpJo}O zKSaWcrc5=FDOQfH+BHNLRY?N9XvZZxq~0`rrzs++MV#kkkzE?(dK%atT)Mo!`JReN z&8Ko6$7Iw~Pe^hH;XigFqN^(KpQd2FQQ&Nc-F9GZgr{|rx6La)lx$=W^iIoiwV4-G z)8Nk1DJ=tUakKG}@uuUFyTi^!>(P8#QKo#^g!sK(!7Oszgvez4dmKNW>QG+3tV=g|b!x3u>Y{HE9Q>dtfOCo)?ibf5kEKt+p(3D(btO+XtZ^1Xw z?>Dy5F{>j}pQaiY%kmwb7^=DUzfX5k0i17MPsv7S(V*}(oR|~RUt^$W`SI>dW67!T zksug)+63G)1BlTgo$(iFWWkzVAT1lv`zuc(+CCv&x0Vr2!;%tV&*;W2Iga7vF}REK zv}ShCL&K;Kg81g0w1;6?M1z|z;Y?UrbkqqP)UP0))*t(NT)EYW(&8)3K$Rd;(~A5Z zL7UW)zVT)Cd9=1}B<=F-{B!E;1n$+FSXy7W!1p)FyT~7ne%Y2jwnMbCDG>EmFv7-_ zSoCinwq+R`P3ld6bdl-NQr9$r!a;YS{XGQVREoN7xVku|G%LmlQ3lY21F~@e(Ec_X zMbxY~3_Hf#G}Af85}qc^A)@SM?=l1y^DlkV4;$!UB7A#8{-Epb{tlE3u*hA-j|!93 z>iw!`_fZ=C24i1;MQ!}A8r!TqE%+Lpl3yp&B7mT8#&Kj-h;_g+L!0qlmw|ZQwSHn3 z(5$DAz+vGKIl~-RbXgpDy^++lA=D3E1``-G;JVnvt2cnMo=a_*T7bSXs&>;Z%6${l zl=sLPna!XiAuj;VRD^{iZ^%=Sn>7Pch>y{ERvY4Ag-e>|UB^Zz?98-JaWPzFU@lPq zplNFy_)=gSRb1hpc(by{OYfDO1G6Own~Ppkf}6hLJcpYLB9$=KvoHwl{KnEN_ipwnY7f0xXD!<5Hj^FZn!g$b{)BK^Puh0k zTwX`ozv&u{j`N8DgUPcWIMhg(;VRJ2ca9a*Q@ga{2g;_xSiELk%;wLiqbF+(N~<9Q zxe)`HGry}_8^gu?mJHSX`5M7->&vmhz@W~ILH~eD1l}(VXIoo9afruJ*?ny$>DR-_ z*Ybv*QQ?Exo&1O&hF6}c`$tHqM8L@$RqSQP_a{JB4gXI^s&R+)IJ0n}wx{FnzmRE5 zbDEghW~A<-9Q*(y6O)F0>C^Sn^Y8@M-5CAE9RaS9)r@HspcUnP`G*H!p0%In?O)bd zatyUHZNLgzJuX^8C0_tQ*+cL*-UG)4;)aTtrgqL>(k*(buom(QBGY)eDgJ6{bt^t) z3+YpMcbl+-BC)sIDqG5Z0x{nFG^n@pw^3>t2R&~=(;>GZy}0yq(HXIfpH&YZu@6|g zOI>3&B;=}vr){3jtMmqazSzxGaZ2S+%V9Hh$C?#%U!O&EH!YNE1r9&y4UE+)6KeY2 zb=Ic$Hf#OE{_NkXME2&|_1=T(egr;O?t|(Y7qrJJ;cd1jeO+7qP|0L7bc#oVmkTZv zp9!(rvVUV=g8T~&9RXQxnoCuzE{daTQHY3>=($&@Y>DFi?j3;8$s0SuGRQjZ?a+L- z#I{EiL{pMYTxYZjmY<(bGWnrDUahrc-lJ*nnDASC>wKMbawXXSSg&U#uyubX3NV#? z(%&k*apM)@5=`KhWVc4mAFL`C*D!9=i`5BjW4hcx+PpPXv=K&%t4|xI{oKZe(r*Ys zGI@sy{fjxBsLdn+d{(a6L=G}+BmNg-_t2z?5-te1Y}>YN+qThFU)i>8+cvvw+qP}1 z>z-NMh*`{Ha+Uc5G9&WUv*9z3htl(1O4(n3xuO=I3VM>Cr}7o32gCRWnw-eMG1j{Z zwT`}qbPZnf{j|&^HG*&OjBq1qOCHzF8?j|7W!GVP2urP*_C*^PeKsj#O}alxbW#Ls zW-k~e;Bjrz)nm%^&8jGSP5!~_kJCXywx=56hdJJ_-*ZkP%KY)CSpT|Unqkhw6!3ra zW9D^Y)T{u8{r7Sq7$4W!&?Q?>U?30l(Ob4MMELQi+$j_MdB~%WKiDtTo zznm8vuWp<2N#0AHy`Y3RJf6tPJ=GRMne*lCBNBR=ATMdFxMNTNK>G?Na&{9Z$fMSE z+Lfa}c|gFN^1r-=pAipL@oYw4+J7DK!YDJK5nH`AM81p&06(g@TQ`{VHN~rjQZG6q%$7sQCq_g_H30Gdt_$p1}i zV!2Cb;XUE9)BoWzQtthlC{fNn43cxCQ$PxQ9Q1XDr>EB~)-J+huli%AFhx^)2qb`H zP<8pBcksL?=DpcZ^v(pB3n*oR@D1E-_tTEPwp7je>VP39}>IgwrGAAu4_Wp0+ z%ru^FvBD0nb^bQIdhK{{lRmvxtTkv%k#^uVL{}}SG*#u(=*ueHwhHsRa7HeKQ2H3of5C#Zbo(H7eE+LuI>W&Ehs-FtTES;{UT0mRbixu$9%jj;yMT(-YRbbD z-Q$`48}aXc`3q%{`^EZtBy*YN9Zq`NRKsKJcPNqz1nqO!;KKTHHtKHB$+PVuIGWpuGjQ3K3okHhqL9+`yPZbI z4q88!i77ePaGlJgDd;g+kKNRw*5(z3%)LQTv@PuJ{OOLVHCy}z$Tv8_YKlT}Jw-x5 zaq&_Gf22o2Q$j*)O*#|oWX@rvk$1a`U?{kq!$_fxbby0gN)O$7tq#^2XU|;%OWjbB zcb&1SPyiZCZRwRF(I*E5=Bo0lF9@(e%aiq!;0!;yadLZ-yDd87l8me4p>A9V{QWOJ z4AOk5>>q0<@q}YOtESkifvLoQ*u6jU600+J?XO2!nj~Fy|HIxbJ8xSqMzS|Q0iBp? z;om>sWY-4$N18AdrCl``%2CCgh+R#X*Gl2#e*>kp$zI^!)CPujI(|`Ueplm%%xJG* zUNqNbetG~}s6p{+;yvvdqO6VuQ6GU;XdkU$%vVq1cgLrE5=Sb3&!&V`N`siCmS87H zt44yx?m@Ns3TEPG$xD_>Xe9io{4 z4YB@RX|fWw1XYj%YR9U$w7&letHdfz*fJiy!+$$GpKx!0N_aUOL&nDM_}&o8{Q4d? z`0Hk%!q@FJCQerVGIvzELo;mMS%HjV;>|Xr$Cth9ufkaqGjqnSRKE^iCpZ2{Hk#v3ul;7{_D>|Oi?yul85*{8RI(O9_4uF^z?|VSzeM4o-&LS zE%mLgmRq#F-p5yrijRFJ<~PAcdv!6K&Lq#+(Twg&Yp5(#GCJ2VLJ;?`m9fpCu~K@{ zWEncGl^z@NOmj;IDN~YDN}HPN&eMuG&tRofcEqjcuP$b<5Q1jNmpVdR-3|OmyNEpolm&){m0ljP0{0AAIy~kNh>?Rx0qjd+~+r63*~m7EAr` z?YP2kaAb2W8utWh7!vc$dxcKH_f!b&tKt{z)x;)T4l?|#0W+^32AQpwpxe{Y4V%8! z6e#Kt9c0Y=pZr&sAVduu>_PP4J&WX3qVYcSA0qdqzv8jRT&&==QT!O8(tU@!MN$Q^ zO@reqocGmIb3G_Yquo7%f6Eep6oB+h3C(7*{zxe=t^)A?wT)AwRTmSq9X<4srvF%Y z`^?B5RA{433Q>wjCOT}WRwc@^m$BKQ(vPM?{A!(l?I5rJazc?0<`Y=GLNHCe(kF-D z+&EX3rZBaR9^LU|PapkU?mp@-)U ztEG7rjdr6^=sKlO5v`#OUJ*9P(NWPwERtdh>I$G`RkiI26HodzAfOwj)wc=xvzt#_ zrm1VxZm5?7Y^0LghhFE|l|4t1p`x)T>@&USfgOUd{y^TqVaIt;FS{p(*XArf}X~% zf5l`w3p?rorL5w+l@{NtfQKK#?`!|gbQ-(Ql+3*#z=nSRE*l(^j3QI~=`j)NU$_Sq zx9?#qA_*x|sLX}05dsauL-WcpYvh%$kjo5&&$Is^`_Uflahue{*p81QrF<5;DHX{% z1Bw)^Svbt4VVRs%9{l@)qJaZVF{Dna;Vvmqt^;rTugk~(Fr2G#jZ+G??WD#)~sa1eKMh%dnsJct-h1VHC6G_x5j&OAw)8+s7Av394WwV-nF zBr4O$;G>rAIzQIfuU2Ut5#8dPAcptFGRij>V~?F?!~2Y#VrfPdL0@ut8#dXT{j48T_x9O#PakBOWVJon{62O&3AKR6BW0{oNcGcAhvWvWqDQBUqd)L3o z4KFm-_L#Jyx4JCfw4dAf?c%j3OHF$2+2jBHei>P6i|bu==6w?6!iG5}h4T<99yESm z50mhZysAK)RE+BxO{Z7tYOY_sD$yfFP=3OfXl=I@1=z$zrZ5m&#QQR32QU8HJ8D!1 z*JGStMt<9GdgV5Aj$eL#UsRLGFa^pq@jJJq5 zsjOfT+{yBaR?{vg3)SNh`Pc7{IAN`;3?!Rv@&MFo3U0Lh*O%h+dQG|dpDB54*St(> zQ|v{H6fyVi5kxWtDc`?Z)GH+|L{ZdKMXiN8Zm&(d;?RJO5-lGvv0U}|Z#uh;s8H;# zNpJ$wFZNJ+ZjCjid-NIMr7v{j`}j^O83cy;s-2}97^OF$gl$HRKnSnTozp3y&5keF zlSrBSWsRD<#IKG{fZd08O9H&76)?dyGdOGXDJF`ZUd`q=y*JO=#q{Nb=Fy=h6Ison zS`rO3HaqiZs&A3Z+oc-JA226F)VCXAvumlHg#ueZ->%I2G>q$cs}Y#|275IsIePX? zdXNp;s6W^#ez@l z6K84)NcHF|;e&XM02s4O5}^Ohz)Cl3cescn>{-AmHnU!<*wdbmPk$`uw_BuL6V9s; z-ouO*o6@v@<;K)8aoEAd_viJ~6G^T=`zQ@KXr?KxIyoD9egR%Gw5x?lksbgp86C+&( z`EIR}VO86=2QoutEk6p7N6)5T-wrG{gRg?jhsKEVsai`)o_4rGCTKoB4y7PE0{;T5 zH@JZG)fHz%N0z=A=b-Q48fTKVTKS5VW2fXe_K=Oo2C7Eq^<2duW^~h!v&kCNjmh;h z_h=m@W1N{&yVMKuV|PduWl5-mW_b}J(r_;KTO;6n8WoTi6pj*fX$%Vzdl*BZYH43j zey+qMQW_dOmGkontu!$IjLw*A>+Ei5p>4L$UlF{^nfJ%qWj=wq!`ijkVt;XYIeI+a z#_xbk(3TQ2CJ@OuE(V-hqA&vYs9APDR8+svl3bN2+i4c(h6V93zCpW-+F>O3Su){a zuBKHm-nD+JQucie?i%GU(|`&z?!fIfhmTToF+gHT-z*v5sM(EpsU`f4O>`W=$kX|jCR(xwGkl3qh8!iCHHqjSwU<1Y zMs`w~_AMscL-*(}RoZqd-QE|BFglhB5^GXw;egkTK0+-1Wi?vqJ0;j!Nzdw-Xthh` zgo6)*8ob_D&>2^Gw{8u`3x88uCb|0dTfJ2PrPxND%c=zX9`_)HkVv6IHDkH55*}*hWoJ5KwKQwf? zK`C89r8xMUx^D2!yCqdhJjcm?F_8-}b+UMK_T3O3(i=*dLo^lZ1;6iAhyJ#Enyl-i zw|5fkU}b!apR1ijEO_->n!f^zkpWg0U$=3kddzc1h61Q2zu%)r)T;H5-y(9TyTy|0 zE>X2r^;sRCv1Tym(!1+A5G3W0s6m#@eR}$gw*nJ}0j9?bD^-UDVtY0k%q=sfORAD%A=Rc9Ei{;8~ah zRV4rq1XEiE-qM#CO9O7T!rznHqnD;u_i{_?u2*sf1lv2rrBxgk5RD>Fl|Js(HLmpb z>yBa~{pfuEvluPtrO6NrT7q8_O{AcKUT9dt6Mc%3MQj>d2G}4e(WGtev?`y`UV?f# z{IMBNk1^#aqg?2d!vYBm877kE<|LxpH?aw;$U3E`!h8&c)AKZnlTZah%*VTH_T2+9 zBThlVm!2fbmYwTV!zgb4McJSoN>lhu z>&U!HuZu+Rzn{gv_ueC;j8GupBdOQmqfp~|)UX`&^5eWI zWqyc}AV1ZMheNTJH0=%#AJb9}xKYUfMn1EY-8X9|B)0GD?2WOIS_ob3G15#fF{Vyu zRE9RAz4Y@)#@xY7#n~6VfOl5o_;#wc3OGqtW~^i2>N50}F!Op&R7su2tSY-aW6k9C zMwqabvZkChl%5CJkpY$sV!!VbxJ;S;T0BCHPCVir76jy1d7=HrRSZZ<2AFpr6eNz$ z=A$_Iw90%s%0w=+Wco<2VBtw}HwN;5#_eeNBYH(4O4X|F&Y1R7**oN^>Qm0)=Zcsu zK@gfby8?b$1=t z&6@?|n5`h*GiIH__#@nhfvkX7os`QVh-$8m={Nu&r+r?sz#GbabaJM@&wWec@({t4 z^l8?X!4e<>uPi31!XUz*;r)IM6j@gQ4LHAwf!==AB}md-8DWcr_ZX8?+edjVDavtu4fus6G4CQ_9BJ{X#SL1{mnlshRaK=u_KNeNOEfy z6)4FxKa+EwpFve(a+r~U=Dyw~RI6R?ob{SS{i_^YL3b06o;+N686vu8a^!yUVwyJ=<7eEyW_dK6i1-ZKX^+JhFH1Zqj@qt zEh9aOfdosckY4ZYl`R0~W9@0y$<8=ne15<9*^jnU%<%Bi&oxv2AODc6oi493LvGN( z=p3X^P7FrDu`cE0!g!9`Dq(Kum5UhGZbvY~@f22T6 zsghv(Fv2jv;Oc$ZWR`_z)00l3APz~bbaA4rvHg7}J%F)|%?(Ecn(1!*PZ21c++uTS zLNu++0glxak6!<^fxRgCh11Z+nn%8)npyJ`MEB}_-qe{iaH9$#7=|tv&^u)h$IC4a zp0?WiUg~CHo(i$Q^JyUPL1QFhaOP*c)J$5He3m?*krKGD&W8myLz`7T9JNe596-Kz zedQw>F*rlx)8=ZLU=^u#2?nZ71X9L=b;aTxo%$F8(wncAY!Z%KlrH;Tls9*nK(~fQ zh%-g(m<4}pVKR~+d}u1bMf65Ujbh@#4istfq^1`WLhxE6XDW+*$h3h&l?k+w3B>z~ zJ!i#5dlK9F*`1m5x^$ZpS4EE#$D!Z#-{^oLM@~YM>(gx#Hv1sIZf=SVBGYS#L&zC> zZ+kl`Y5`D=YcG7j&*Hkr@g}RZ=oNo%MwxM+g?yzzAT!H?JxSFj*K;mTit1*Oa|*>8 zN?;a#GR8JD&<>|Zt_+EVQyKn5{Km`eVUDzDUcG{Y1s=yzO?nn5OCcKC^ zu?t(D((_!4n@K_vaz7`3$F0bt(-_hUlricbk{ZgQMPj$&1QW1J!VtX# zb9~wHtcZQfI4CE?7+tFf7YV&R7Z9VyisfO`P$;+s812=_zI6l3w%h^L6U+j^MrsxU z_RfiQU`oChL%fNZycxt~Mmqda&7$kmyOZMw3V@D?svN zvZs*jA3gs`rGY=7)Y$<8=*^)Y*y(kyC_AJ+76Z@K0DB=I!NuS%8`|DTAN}0V>-{8S zU?7(E!8F22%X$u|>LN>KVHj>#Sw7ArV%Ea~!rm%R{eRnX=#yp2{T_GoRESm=pPP-&e@Oq&~N~vMP}T7&O?wg?^A`$6-)`@ zl$-rjfKgQWZ4^_+8wCY_Sx}n?-bKCz@4reoIIu}8GLMi@;*?)iu#=LYyA_AIG}*S6 zgPH?sdzLpSfk}s6a(HD^8A@}@4;1@3&2odRA>)A%~b3Y6C+()v^lqX}xg;;8$UaU|Kb>R%yR)7~qp?jog1-jgRn{YCpPg9?wLz9q8EpXkSNPxeAgSW=XJ`oJ(bb8xh2_u-UorH@kvYdufI zA}SW5^7_p)^x}yU)Doo(=U0@iDPJ76Yk^xQDO%>UkeknhS|dve=%(ibbivR2rJXxc zAdc6Y95WYhvs;U>TxLAtERR@3pI1MYr6tV{46*97;hm!0n{KbP`$~0T`=J2HI=SmK znHZyt~i=lFIbBHDhH22X@I%ROL$W7qWl}_FpH{yqrtS(WUK{ed1ZGIIM zu9t%^>i)JPCIFsc^Ul{p$6ti=D)dc_R6zES8obI6O*r9jX8ZK57|Uvpk%$qcS)!y5U|eKACS+3hMG$! zlTX)H@mtV$s+%1Ph`vj*Z<;6l8H>J6%X-gQmzX)viJtm2<1I1?@av2r#u=SkM@*}V zf{X;284Aq5VmnajL3+j7GW0RgeulJ|P-JCqk;Tla$ceQ##Y^7~e?|JV zT3uu%{x2jmGB@RWora&3t;0V&QL9*^Ut~^EaE^{q3}iN}8*DvTUoDRc`R@U(YKXQ6 z#FAaob+x4>*Y#=D8aH>%Px+S%EvX zozAhlahYY4L47tna5#E<^laOT5U}(tDnQ`x$%Y~mAd4@Ofmq7>*d3qdV;;{r`2^c) zM3iE1jMo0>?c<`QT!~Ci(@tMw=CU(v_QOP8aF(f#2GmkMeqCIUmp7h zU;U}}yzp10mtip5a!8HVNAEZc3V5rRii*h)^zPq%^R4B}`FQ6-_H(cckTdEj{W z^~gdsR^^*xiO9o4?dGso!QwCO9t*F$I~{0vb-)QirsMjwU+qeIt-)XuT6xcdD+QZ@ z6bxuV|0BJN1g~=aUx_xdm0tN}ho!`|Q-E}#MN@(&LxYk}b(_-VoR!On%Nsz7og0 zOjk4hA@(95CCrAs+=C`I*uAW7G`9l71G^-xq1>zgx-xEg!gyl{f{!I_fqh(ZT z+fsbTsLea7Iiz+O86&=B0=-m9EVM?vYrj)XNpo4)qIGq5b2`W;%wM!;P z;sy+&qH;pB2Gc@rKAxu`JfL&yDJVXsQV&I5d$4jO#$Wbv4MK^jI&jr40d=haUvmtg zoZfPVKLa&Fr7rcrHBLI>a$V>vw4DNoj}e(H9F^SpO+@t6ANlGeCp)yCvN!#$&-Me( zgkMjXjhTrFQl{*8LadcVArUz>XNA*3LdfnOvXe6nbf|yGW+b~U(569Ps`TcR+Y-Yh zUFy3kUAM=%Z!l%nC6o_D$(kiC0>6bi6C4~3vbv7 znaWIN+%Z>`Uk=~?7Yg}GFpxMYQIggDW-G4AW!N-0m4Wwt{{wC;Qiuqn(NHM;UE%%RF5 zKx$WZ!h9;neIORolGS{x<<JcbMUgmsm2`nT%xo znyyYz`;5sVS^he){r8J6_pRL0HhR+OOM7XfG5m zRPP)dGe^M9*AQ39Y#c(}BaB9YT|x+oG|EH5RdQn;($HO>IPl|UuIJLUI(%|O2a^!K zn{qBCA?2Mq<{VH8V*K?VVGUiGzz){2!=}&NlRhx^WXBmA?GW1Yj1otFweuoXwTlMg zJsW2Rs;T=Ju&7&$gB502Mbu#N2YhU~XUNx7rjvD8NH^}0ob{(8Ef~U_T9FTb2)-xC zm7NV<95Zs>Fu#Jl`r3IuU=qb%H~(5JOkTr@@^^jFFJEbOa-Vi=N<3t5UxD^+0TWV1 zl%?q<9$`zgTQxD+E-eFrg{M&~ zCkvyX-+1~~C-uOMkX5B(zPX~=1;yW*?sN z1v+4z*qSgcQDuoy#Y6uZ;4TFZK+SHEW84|{!pNG&QZ9axapuUfxIw}*B#K7pEMf58 z*#v6^;(c3Ud#Eh@U7&49B;?^3smY=CEC4`OHRUIaS@igVCs*pJwChoDWaTLnUNMR$ z8>ZHIxm>i25s@fyt`rQAlAkBp7PqYuu8;1R;}KO+XGQ|Nh!9H-*(@rndOpVG8x(N{ zycqBL4=kFt+K2r`E(%{yNOQQkZ^(PW3ND`A_rN8WK6c*+ApE>LDfS8Dj&tDKL#};p ztjj|c+fZYwwlg4wCKf(4DVcc_VabGdTxN+=d8!tMcqYw%)N6bwBgmx=ww>TvB{vbr zfJAQ)7Zn2hvg%!_61ZA?Lbl)2=lr#!G&~Q9=zMI5l5EIFN1z2ZC2@P^88Q+uOTxjz z+>F761>R*=NAY$xr`a9U%b=Ujo|t`hFpBPReXlWf*wBw6bDX zd>EGL1OABL@hr@duFIaZp9a2Z2>7Pisq!%~8PX-EFD@f>CV8k~4-%QzMq38l?!y!5 zveVXJVJp3YHZrN@e76#&>|y5&DPCwIbMYJ=Ykt{fyj>G^dmJlXn#O zo)zQnwm(%_Yd>Q&CT8xq~x!C>qELLzUqyUnTQ< zP}FAei}z?)sPxzk6db_i4!wOyS}YZdM*BD6iTmF1FJO*F;->}HXzf|~xQ!c`K64`7 zFBswmUt+SG``=v;hLf_E0pH-nz7YMfcfdXwm}aTy2+5Y45nETr53Xpf&5!AIbC79) z?jr7QNQoFsA&Np{--XdEcRfkhAe6U59=3?E-SLh@-QE)`meZ zF`uaHtl+?O@uz$+u&~=v{T!_3nu|qJuq=qsAMQ5oEdVL+iZ;%iKqO=)7b(2G7~=^a zd_#C&}zbV}G!HefxwM1ayGZFQWh!i0#yV!(kQrb>EKs%J%tC~`4W7af{x2)hiqEf(@A z-Dx#^@DLqvHGWLtCNvfC!vSN*+Df{Q6VTOXh0Q19Va~QIwfC|pTdssXX z6){16G}t}1#{c2?O;x~3q!KfO!=0*tm`wNp;W4DKswtXR0JKyQo|_l)w`17KOuRhK zV$V@Z%2F_pxB6=O!rh?OGcv&|&m}}yV%lgFmol4+D1vrWfu)p$S~ivlPnl)Ia|wF6 zYE7r)YpJdFKJi>=kV0zFO`71N1*_BFDXinJI7?JmDyAM){6ah39jl~Woga$$#r@j5 zhW1)cOEh=HK|Tsva{VClvuF@M3tNQ8!*p@3!>2J%tPEnHM=Q@#T<}*12!%P9-z(bF zR_lo>oDqxRh_%omjM3vad$9G0uL8iA7TB++CK=J-4-C&HpB0ZbskRErNy36dt^CA0 zN7ZrydcuYOc*EFRuIketIuYV|AY;OhM#m5O_BFSrbBtSIpTW}^o3Th>2pU7oCPvg)UqXL0vvpq<;JFWl{TGypg~||IwAoD%Tgm^ z&YSElQ1KxtR%=4%ddfm@ruf$c!%B`5lJ_VQu+btql4!R~((XvbxFFf8Q>GbWe(yPJ z>wmr(_l5xxMHA?`Da&9~*=GM)8Ld!PKdfg_Ot^MTd31!;i@B`!rZ2s@D` z{Cx=^R#eHOGP#%}EX=x*pjsVEp_8M|AKzgoqm8NS7KTI**Jfz-LJi|YRapufnMS#( zjdMGmE^{@bg3JhsyqZb{5%oh+_|Z;>r+YM0XQj}McOC3=Gkbi6PW;U28i}aBxt>hr zAg^k@g||`}AQ#wKgpO0iUvH*yd$GN%rtoCe>f*l%XAI80dL4@MghlJ0_vR4Qz?Ybnb`}flgiOw% zTyjMHk~B`}5$?3T0YH9b5MWg^`US1Uf}#ar~^O!;C~+1>+jI z5izcwQl>rH$LCr(NKU;QSH3-N@keK*g(OLA?L7{_{;(Idk5lBM*qVkVN|&^ zJ$}*F0lkL-S~_Plkh;Pj4dnm3bMVMffJlsXlVS57-P8ZnnWLOd6j4qPQauVA$q+U% zxDzKrwtHgvn(Y=sF)P89R@PTCv5Z>t^85JN3AUqXxB#{rQplBkr3X^FjllssAYR6d z*b65?v+V3k6%-5ebwp)AS`-&GnE09u6CO4gJT3}$hP4KQ7`ZIExP$I#Dw-KV>u0{J zd7BpUsPF+%_To7Woq{>dvi(KVTp%AqBW3&X1B)Ra)~7d)S+YX9l`dr)Jw~vzx>v~n zpHeONu9D9X#~z}x+a#tv?F>r z9AN9j(L--eY@+>+Y8WAIuPHp$K*dP}j#H0QmLP3)Q^;&fZ>>fG&&vg)nRnZTxITcc z?AnMP`bVUAm)@@}we-#J3wNWSk1av`xNE4mz7l_q9_!>X5MX0O|0WajtN$1%TK`UEx5)s_vNn<_*y>!wOK(Z<1>d!U(*8n#7LA{nbcEYG^W^-Chp&lxj5lPtf0PA zuS(oW1#HBAf{mAVNe?#)(iAp3scW>{Yc8gK>J}}*=TGTo1%Y{pXhc6pE3}EUv3>L{ zcyCx?St6k|RZwVlk$9A33IR)g8#*0iyPa)HQP{oM#l@Jooglw3*}MuhWzV#w9)G3X zm{+T|O`GqFqyWEq@#+!pU7M&a$XLv13-pN1u}(GDlGCn1dVX2YEK^nRnk7Oi0_NQR z(p-`IRLyFKassHCkt-pi^KsTY?JhR3wHy!lHX*m(xA|~0pcT4TymD)m@PPO@^pzx-5C7hVvgrNrUG!-m>A4A zKBB&_D;4&eM1)wpowt=nUv`Q>PYaA>SsGIb(+7Q#I%Tw9DAjrWD8~@qh?xxL3U#yn zLKeXU$Iw7^4-_Vd-D_H$w_aA9f15av2sRMzT->9k0fk+?#d`jjPB>)wCmc*Rbd(+L>J==ekBV1=)H)o2hT&qqGb`UZ;JJ3u)@o9O0F0tRp z)chc`LE=S8!dZfUXJJM=NJqc+Q@AQh#}ghpzrc_|c<{5SIkGR^TkXF`Io}h+pJ;;r z^ED8Sn@nkE{%cFgHkP?774?iZh37VW{Q8@X+D~(rW||SV&a6PwB5{Qm5LccSCF0%l zE4e;<0S{lWFWw3u)dumE+!yFOyBQ61y2zzrKx>h=;Cd9sK24t_zcDT%owzSmWdP7X z0W090Y$9Fd4BRsk1m2l`yzBAsWc(CAvjb;h1$`DzBJKOuC&SymBxe zJ*@13N2ViEZ`&vUZC>rTqEUWeIuL){@36H)pvTJodW6JTMh9f$6K1C|t0WTH4o|-5 z&@c6Pu=al9T&%hqybgM2v}jV)MX)t;2QpD6kds}W(ZxU(b@U#8o(#nwuU`qIFQvNO z4~*$N>w$e7T1<8~vUTDWI)9%=^~V}7xBTo>Pnd77_)fyzi?>eM0UJMd@G@6Vyy#vL zllY#9T1|{cROs3%*97jc{{eR>c68bI+$W%edrB-HI_ff5kc1?!!hF^5hJXC>YIHO3;=NchImMhmTYwV9C@(-r|Ki(p;P9AB2kv8#oRH|upr5xXii~1`_ zRgVtWn;TvOyK@PzZ6a+J5bsoE{->-b)%>w9r<8(_DhQd(BmT=+gfv56k=OIxT~wX{ z3$rntJ^Ws~FY=OZ`}FZCk{WZ8o!JRjC_z3qLTMgswxK63!+Ihe$PXBR>@PzozyT)< zdk2|Q@+m;Lwn*k!rF)I$7F_O%GX91=)FCOH#WZ2%+OdKiiQ%5>q;Y1_!h@%d4|_eU zJN+V$U)DMK>&hurcDr(aHn=1niUR4ke6*sDs>H{TWQY0s7lp^OsmuEuV$0R60Wr=k ziR2sexM`(MX}g$C8-~>x2buf+Idg}CIx@UoEXFad+_h4YS6fzd<#JG*WBwMb%KEOO zYb^^2c^)}bfAxq$xEop`ad{j#%HS3s!Oq4kfsrH#pj)8xM?e|l*nYri4qY#|n-x?ksbOm8bK$e)7ZjI%CtZogh5aaFWd z5AUmp4Q6CQ1ucjnCl0f!gWiV)ZH%5*`_buz)c5Ye77DKzcFnNsV+$A2UmE&+S*VvJtSNRrEbO7Ym4{^ubI{R@mbJ^&JI^i6>PFM))NKcsr;{C;oH+W?Sb{a}!+&33| zK;(0K9)eX*)`>c(@J{iY- z2Mmho>}xZlJ=7s$B{8aY0^TQ`O<1xr$m78#TlHpJS7S(TfVU&8b*jdjfzz}C%=4Cj zakljKTm@wxiRNB-aKlS+ND?C$&gK`bjWUCwo1BFPL2S%*O6H{ay0GWLDgNbS^f6m^ zaKpNeFtsq1j^|F@gVd!%ril71CRueN2*cPjuS-UvsRX1=H|m>i0zNP|bFi$OBG%Oq zhF_<)98SAE=3c(JpMn5R5OGZt?K>a-+*TPK(-msdW{45*U-|6t9s`;WC})HBeTIFJ z@gEF@1tm}jdiXt#Gxrav1OzlM#7iOAl>3WksGe_@*c{2CVa|2?n}v2wC=bN}!0|MqKGxtLkm ziT=;}|M6>_9qjfN>G8bBPEoi1+HSc$4*zX5{cmSYxp9EtZiD;gKHG8Dv#0yA@>vde zJI^W}yRKqitD>Mxb8vA`e{y00lmX0*4uLCVDk_;BS($+|u(S@wC&U9!N9$JO5KKnv zPWgc3omm5_I#2^L!TSe?hv6gE* zf^dZNQk3PXX=P(?d-n1R`>zkmQuD}v7|E^STT-Cx zx4lN6tcHJPCgpzsNI=KpB& U@I@o#r z-yQ!c<=^`;ipr~rE9=nyPZRt*Ebd_HXlCtT2~c(YM^9s*+5b5H9ab{7{!a(}m&<=U z3c&pT*7C-#KxCe&wnlb|A!J0arE?|XJKXn(6h301DIJkIRIR2Y<~ZX zuBjUkXzt+pZ_)p$<-hBHHUXrTX?0vc=19NB<;eSkvi=PGwAAU z-?c7^^@x?3;lU;Dof>X|2Li!-kO}|p#g6n{D~a(>bA+SmnONdT{T1AoQqn3*|GKR= z<-*s-NP{Z~4IFz@1&HgYTi48u^&1*@>4D-*$&y>+mNf{XLrMZ|(s8j4UYqY*)Um0a2tK61#xr9DPGQJ}y4SdWQE2P%z zT40+zRh}Ix;lHzW>V^mo22u*H#gV>S)kZK#zmY%NPLdQ7w7m=tSfzu@%{Hs=?F8)+0jmI;JeVQiPGYNp`{=;NMYvPoXEo zCxu2rvnU{{QD4~?n?HpflIPg+x53rShpBzbK+IhNi5^a)oL~9UU&kFNBSf!P^IEF` zMIOY5M=a)KKC}}icJ(v4r{mf^qmeizQ2Pp83iCwHlz<2C1@n}oo;-;-ZVU1#BR70`HV?HyzEuJMvFQfwVl zBu!)GUg%P_yVng1?{JspNOV0`S69*S`o~jnvp1f=sp-tp7F7BRyf!p)FPxfao5MpB z2%0SwoTem20u}X6u>+vd!to&2SxYG}P>LXOA!Ak zRC_{6OBEmj!sS5K{uMyzqrI?_k(wp6fxNpVjkDeu?_n3LcIiS&=E!8%R;K4{z@{$M zFq%nz(BZa#)HtL}q9Nt}E07WPahcwO9s1I(AOl?O*==U>kVrG|IgiGci2({B$ms9W z5<1ss898AiKb#Zw-QRJd9zz6v(~2wKZPvA04*p1l0WRDfse_esxqg&tL;B9#JFe&A zP((U8hde&fcdDy*6#_q5!NCV$Wis{nq46rbQyzW;>^1p=xO;&NjrmQrwaKj`1A#)c zPNOM$0%k1G(>-Ha5MCe^^ zy{d)sQVgK^6z^M!j|`SqKoInDO@v0N%VOu| zXR?YZ7~>v2zj%jE8S^j3nca+_Vpu&l11Tms>z#ord+1+Xq%ETc@0a*4yD%JD7&L28 zs(OPQp@wUmVaUZoqM4}*UO&n_BPwUpo_G^41CdmR7d)WWRYx8Ju`0W67u!$EpLZw7 zW=VvC@KzOKUgM1jUirTHowlUQA~BVsqKuR&!t4$=EHc_bV5tQP=ydPL`&JtFbGFv6`ff;GwQcWRKWVVvH=$Ha>pfr`{nX({2>?ry%L78^!2*A403PGK+x@S93M zJklTXA?5Ft?$i@V{YbR|Y z7715oy)9JSYL&|ys|Qby5Dv&N5|-k}pvt2JfB{Yo>H_w2_0}p&#f!k{!nv%^m$S>a z3nRPtAd8z>rO+p8cF=y@NEo`NKtx@#lG~b%1UzxkFv-#L?Dmb4(o0$TZA+CD?2Ay? zR?Zj!)hv8QHPvd|UZ#lQJmbo7({782Pul3?sf5>0O50nF-}{ASz%tQa9x7i)s?H&U zdjyR&Ht~Ytvq*jIDO4dQv!5UB^rc(;fyXO5Y)kT$%T?)kfBt+UsfDQ4?U4F`8TYgQgn^5cU(Msi*8RC5stKOkl@;xFzmFNYx8P?M^f$LCR@PwDbwIMT2?=IQ+sM+@d70W;TJL>dg0G z(&ec#a3W`oaZ>J56Oe;~TJqyhtQ;s_;b~v=9nf=6aK#Vd!M!vf4mF82kE!o(w&IHU zszsB~zgYnv2?1L@3h75B9uNaZ#o|dOpMF4i)N%@Shm7r{D5dBy;bN>w6(MPPW3<4r zJKHzHBpkE1XF(XikR-i4&V27PE9g>-$__~#ZC>r9A6l8^&~~n{tYh%{-gfRp7m?IlLJ!8ELz=~(5kO&eO<)ud*J%-B$a0<6BJhmZ1jyAR}H=hvBLy*cANvYjA`kj zQf}=lSH_&uTz9Uhkg!Achr@v!{tl8o#}M7Le8KVl%i=u)RaRj)?LC$)E)1F#rrWSW zW?&Iwx8TJlMH%x;pp0XfL9HAfj4|ajFe&UtLHr;%`Mwe6F z4Q(zd{DM_oT>~bkzZ!4o+M+GfYF(+OKgHX6a1w_9C>Ze3An_{|+52v8QyA|>oBiyW z?uGKj&p~9@Uwn6@7ezM0-?=P*^$#Nuu)G~&9dBt1-V6n2$VoD0gYSLCZ*)4Pxv8dG zd@T5+ED=}eIYQuKl;5=uJUn|Niu{AEn|EANPS>e*3~sM0$a-#ycVK_StM>B+tu zxAdHsD|~*C?!Imsu-wLH`@|3>v^H^GoLE>wQHC+-{&DKiB(Q&1{OXXrl-CFFt?Z$M zxxx7h#ZN+KG$nKHb&<_-T$8Weue(ztTEg1>*W-Lb3!~>af-Xp|v9X(8alk7}^dQfMpY&(=UH-3(1U{qi ztQJg-opPz&*@b}@M4N+s<=YR+#c#!g@H_6UBY|oXLn_T>!vq8VyB=LfUzZE}VNC## z@r`?{2F9aneT%UQCereB&`*UcfK?DXG0~ultg+JSoaL!kEg_YlhPq7txv7IXm4F(p zP9cKE3BC(k>{gNyHQ_{@AQm88MqTM*l;9UX!)s~Y))myOjo&t-3|?$x^B{>7H(7Sd zZ$gUuYbqIa+bumPjfA)V_DNHn>BAH1`USc*McKfkALNN4ZUZmQVhft;^G7hpUh!xF zs=bY`f=j0N`hKR7M3|J!-vhO+h5jc}yFF~vyV@z=S@Mxe_d2NqFN2&;gcnPQ|k%qu66I>U`VB#*z6wC$hXH7dqZ5fQ2 zh;8{e(d22k@s^Qq^&W{4ZfWx|od!xJcUhSi=~jJh21MMgUE*|vEUrM(-n+}k%+;Bj zv0g*(hf*=F?H&V^1<%2d43PV^$o4@}OH30SCgP7kn$(L6<)P-sV}#{BO8OK}@3L8D zR|V237=mUGRz8j}AZ`nv;f{#v%zrd?m{+T{j-MFj&rFgKkJ45P#^;(mvQj*oJ@$9a z`OSo}8ZWLv-A1q(sL;;#A^SBN@f+!-2)Q@qi*m1PV7g}LK@L^e&-Qh4SJn85nU9-E zL{Vbc=g=fDC=f=HfzMX+spDi;OqS-gpUj#ct21%R%tvTzmLDHF))7q785rWXwyWZ- zx@FIpA)M}kkGpWn?sx4L^tTvZW+IB(9d1QchalCTIVpgM-;!1PpDa4Dn=YosxBKyD zcy`j3lKMKKo!cqqOjb0GKNl%NEe)l6LlXQ)dA?4%qXcx%bHtrWvC%=6ox4FsJ~eVq zvwTitHjk_7#RztTOm@VKqR?W9Gf@I*qqb>A$zy~;26^evEumDxND;!s8FcJEj>d2G z=V~IuSKov()COfmrE=a~>U$dNdPP$K;Q^H7TvqAp-!h&KKCO;Y;J@jI1l9_R?yBC> z)SSw>BfRumO_ULlo0PQ-S(bVvL#{MIJi3P(iWibYuAguuQ%GvtnWXJDn=m*W-hZ^#rw)s9Oy|$nL7rVBp|` zf?1ksnT1Z>t@q{HLd_>u`fHg~Hr^HZr$Elvu>w`YUu{xEu}dSKDJr z{_#cM_p$9w$*Evnw7$R zy$k$_$vijzp!?wtCpq@9NXes<19H~qew-D^Mm({bs|Pyp9QJ_S_XJ__cdm}V`7wLw z+VwDM^@C=ocDLakYXn+jTXVfHe~848n~HoahDxunT@GZuLV=dkd5%4Ibk2PwxOdd`V4;2zzlG{)r1$aeVk~39P1!k%@4!GrBMrF}FSlQ$9K^ zCIuE|#Sp8F09vg3%lX!^7-d&W+yN2Y%%MXHz1NEV`mD2qE_|tXpOB^J&-gK^Alu$C z<=6GxixlA_L-Gi9SxlfL`u-rSU%nL*gcS_12NkUW&h>la`Yg$-!Cf}rq+eT#-W!Pd z8}g96(g6gH$-_PVhX>bjv8OMi`c}%Y#w>cpX+^~8AT#ZH56!yu`Rkp^sQ}s(x#COt zE+REa*PstEGbRg19BdUMB)9ol%~Li++1baItU`f6UDPs=0GN5x)72fLv+u&n8pC+@ zD6Zvgzwq9pp?I*cDb45^>?+OJdUpoBhKR}66gp@g85wtEF?;?&D=NYttcq>)m;>u4 zWAx6v&e|^rd(iuhqzv6zM42)>GOTc-)z@~2o*9t6SB;D?%ACrhb7KHH%*8oajJD%s zJHaU_ZtMA){{|vXQwI|&4V{H;74`aNe~KE}SSY$3dAY7wXlN9$rfgnUgG<4MO4GyRKO7RCm}1ZRMiF(SDLPGXcSlfka#2fn0Lr zRrmCm!D~WDSxJ}2;xLmT(b)`F~$h^*=hY_jGcW2?Nn|CbES$=Sk6bGLlfU zDArohAc9l|o_=Xa-IYu;QQ<9E~`L8?Ymf>=(lr9dTndk+P9~c`~*iDfW4(eqvJW9t$IO9FX>na zqqp_LqQ`Snx5LY3G_G>j85H>!fw?cUnT>vR-Fi|EE|_o)4+TT@{@=w<7H}3s;WEWY zz|VGXv%pjLY?ut5XEEkk7yM`y;y{|&XI^McP)e}D>6Ve0?&?5&*bJC{9oKd$#Za++ z*>SY5g2aA@Va)Eo6IK1ykdCP5l~4lZ$8DTTYtoXQQWbX{VN2-~Lea&HkFVc>t-gwF zl9gm;6YZm0^c>Aa<+a56`hIoy8TMlJS1XMX2UVS=s_eS=4cGeRvfc3_v{(eaT} ztcmGaZp4@io7!L(OoJJ@^aqbStzq_KGm>4k8M@_|i9BxalF1o^@hObtucD*2uYq37 z<+mRC#wfZz6&Se$%37h=jMwRi=SC*{zOlva0$W-j&NdOK4d?Y=%64fj$Jyf=A+h?` zMsAQ{hw#u*`zHb!ZK;~RMrt7NL+$NEMC4PXIndsJ6;z3Tc5oiI9iLe9)&I2|*~Wn; z$Ix|ISwLmAzI}I%W`?j0R%GCd@yD1ST(2`MN?;1`WK=gl=-PUKQ|juP+}9RDR^}+vKz%aq ztla+|qL`wMYgqU0M;i)C4Y7im6QgK+>tlkJBHTYDWQ^mB zJRnlWa}=K0#c)r&T7kb+#2=qGGI1JpkWX4KUoQ}cCi+`!$zI|PXhR~q`GSV3SgAk5 zWw0zqA{x|?GVr4Qimqy9{Wxo$N%VD)r{a^K*U}{dgg?>(#8CWQhIJDghtsTptCKM@ zcIs*tPQ4wW;V3!{tmJ_bC-aS9ksXvv9SO6iQ#-a~BMK%UV85plv;ekR9@rQF}U z&%E)IPWbI)A$Vtv8tMiYFAWY zyA_RWzioPDgdzzh?xW3g(b<9LGx-ZIh&W^=F#ABda#@j(a=?;XF9prLE2Nqn6z?i(s<> zeFo9TdJ}u2t%G=DpSF;&5fSV5kpNfW43TLRngquO zI{1$(RNl(Ar>9$rp+MGjLEC0}JX>-$Q$>T%gZw&diaAu+EwpYKeJwfk{Rj+~6iB=I zqYV6xHY+MQ0o|(mt9DUe?3DcVepArvaN%ijWa%1>v-5q$Hk&uptmreRr`u<>c?m{eI@*QdMxn*3=H9x#+CN%Xoqs*Nn6Je_}<(e+ACD?^c*Aa?qkmCba4e zi>%W*kJ3%zwA`6{5z4kF7B~bIYFv|k%4HcGjydIBq)X; zG=t((v&>7n4s?lyWfTOg^z5g8_dhtgqDYQWWQ)wF3>wBq{!WmAa+YNO> zVUYrB1l7h;%Cya`P#22ZzkZFeGFkOM@Wr^?Vntna;5i@*OJ?Ye8OxU;wDeONr~men z>3sWbKazly-2k;DGifLV+zno!>{Vhj=zV>(kxC}8(pxHF{|4VikR6wk`!TZt1x?HBS0 zvF=C~IJRi%MmV>O!5Zb&O%t+TI2pby7e*tbO5^bh6}tW~Yk9zXJ8ICi{@nELgM%Aa zq@}gY9gASvD2(FSu*Y9BNlQNJ|87)3u3&@dBc-T=zVx!_GiaE@P;E`Lez3(B74$eOzcBFyfOvqX`-1wMGY-&-uHH9a4-&w|!Q)4!>OF>hCO10>!lU`gV|9%VM6w*oTbT^{gD* zTmtLK9f{*z^O+j`J44}QHP8Xyu~8O7Obk8k8v@9J>x5&Jjy8C;X)Qx%3@~< z%@O*ZWZiH2q!8d32Zm^>M~HS>^NOnz$OC4tiL^dcN(V^Sd*XlMNU->sh|o#~dmF%0 zsZl7qgWJJ4JEdG1TVXRW6^REWv6R(j>tMm)q`0ZRr%MoquRPx?9Qhe-;_fV>Ss z+{bA<9G+>ZF5bjmbED%Qj7rOWtJ*mr;GBXl&8#SBQ$W)r2Zamf+*irfG9EyTk_yh| zM?f@~^j#iL(l7Ag#$nzvo=kA1?zrEc13{N(*{RptX88D>l~Bo9hs4-daX0%8x_klCB3Kzr5OYICit--124sh_4SmnX9jD;n_ zKqh7LKUS~_cC4mdW7q@tK#Q8gSm94PAdPp1QP^oihkyxhe=>#hBS+C+8E#A$fxx}@mtHA<@RRCqM$ct z;g3|X>pTjqJH4ljwdQwqy+(LidB>hn$I8~#QksccJ8V<9qJ-H>#MX{n$g6ft(c#bN zLP^}^G5uU9fH;c^Hk!zNS<-H|;FPvducRcOqh=>q;cKSdXLg`sp>X2V`th6jW6Sam z?%r&uNHIHdLryQBOuWj&JF@ifq&(3do^ZD-Gdz!>FD^Zf{n7S*V>J{Ls`Uam)1z62 zfQLgwgJf|oidcKhJS%!^}lsgKnJOIOHj& z*d}4k zP*Pp3^WpH{GluP_zh&hE1ql3TV-r4W7)~x)esc{^2Eqw0xqJ<{e2kHhI5#huwd1J6x57ztLO&lNPpJO*s_^sO6Yx-QKQbT5NlJbByk|^5;b*t>*LQBNIt21G) zxJO#oGIrirO9#&a9&N;6?>YzS>%>7MS1|pvSP`)*&ByPiHK0hDWz>aNZmIMKIOO19 z0hLcEjS|9wF}@%+6Zua=<}QN33)gi9EF*7@rMXZW0k*?~eJ+?k-TBPoXJUNBef}!c z^@(rsn5}hilda;KH7?DUlZ&|y%eQ8Bi?~rXTk4~$Dwd($(n;dViY+0jX7(QQe=ddB zA@!0qqoP5KyESiiNlnD!%j~TfW=9bcGP3Cf(0;2%8s!mY@g2>6DHYFM8Ma6nU=Q2B z{{-ipV7st}t@396|Gr+ihWX&7TxmbYR!(4sZ1YiyocD}_9_jrfj<63;^LqlT^j5md zDpeAbIuo{{FbtyD!mqw}GJ#jk&`>g(rrBZ(_{;OeY!B75X2YQ_J2->4y6s#L+uS+?iK~oK|t9UAsZtV)K}?uUc}D zMctji43OTJ(WZ>Nke^J5qV^kmZ7=s6tX<$m^n=~mkUWKu{A77N6*?>|mK1;G+!So! zpwo`NOY@HlPf)~h^M6Vk)iw@In~7@`6G&wg37+4zjJ!V8ci* z-Y^@ZboI0m#PR<0TMNX=SKLiS32U$GY6>Lciit6(b{ z>OjpjKdf+5P_xHfU`*_ue?n;~WzV76DWkP2>>w+%BqNieZ1L^@`*>ou@I z%06AjdDiH_uys-xfquLQ&0Kk#rX1bl8hwiWv6Gzj85Px1Mzy&Rd-=ZX9{(oYYjWE6 zF;0as0Q)N}%gIASQ3!0Q4$P|iJx&{>`A-jLePJ&J-(n0;j&(4XZHy4<-85&PY-_Z5 z2f0`>>X21-0Qh|SB*oS*wA^U+%UZl9O#fluw(R4`n#u+%K{%Ig1yuwJ1uQs;o~@zs z2-#AwYnDbKmBeXgsamJ!Y? zQqAmebYFS+=wWK$xQ6W4Cz2t!k>t)?F93Yss7K2JD@;1a7oGp>YE!2>JC*`07CR|!8V8y{Z5h4Ae05cpl=pyl9pHUjUF zzKO&WLJgI`Pzrm_hTHm;Y9Z435IIoO}f9i z($R1TkbsMq(Qo^48s=HM6YLLHYS`Np*ef{_W|^7aqn|+obNlR2?#4sTjuQ~DO9N9* zNZ=rxcAWx8^KC;P>tm02Xi|(KiOfMMuBpN%KS(JD(wEdM^=$$V1>3nO#-qN z-k+oIzuwF4Wxn=R<+~fvgdeRq^p@Rg9qFnbTk0_H%3it&L+3b7c};4hErHY|Ssh6l>W%DhidEBxM_tiwgEXU|X ziAhFj`;SSYPQ1&D{$e`bqCHZ+^5h^b*BX#Fn~n&kU@6PcCnx> zR@EWKW&y#{f3=`#iIP(SB7Py;8WJ5`gX+1~&xvf;ka&E+)K^~$1o#u#Wu8xSwWOkY zzcMlsc5dAr1F=+Gj%xXz+q>+HsC4QMT%5(B>M&*@_JnVc!h)hue~yaAl(d};3C6U` z{T!VhAGMm^@1x=H?dG zc3A!5Nn>Qe=I3=B^M5kJddBYslMr*$t6zOar*0?1l*o4A^DPs9rO$n&>sU!znYa{qSZ>=AfM8717$m(^klWr+3>Isg zbDkeGKqOwO4VAH#n(OmQc`tm7uqx(S{xyMokh8W;X{zyU`q-0I(CLBZI9(u*3%}hN zMmlEA+o=W{VoT0rac&9c{|KpXa13)$wG2~CP^Om4qMW0xB+QSe zu=8Vk2#eJU2%59C2z`@GM%6nty1BhY*`pM+P~6%Z{rtIw1A>YV&aQtSS9 zPQ3t>gNs0Q@9YVSoKp?=R07x*&e4OGy$?RdY?i9LU?kYq8%bFKYKaqAYvxQA|Ku-t zfa7F@*`jOi9WvN#z@YGFwBI~A+|}0h1GAu{see5_tk9=X@Q;tv4A^&5G_0nyZD^|z zjSsLyCiWTHz~~;Y5NXzNoXA#L-?q+_k%OGL=A!`A^>wx;2JCm(PN1${I9Kys&l~X< zOPIfy!Rvoi>u+Voro2*K_#)o!P-$Bc_%c%expork#94A}Y_;rfunOesSpPKvD0R&ENQQJZe!dGpUG496rxqLyi+k{kqWN!q)fWcY7CK$sv{I z$BoM6tt{z&va5!FSV#4~EvKj5mum?hwT71>j1L&A>EF@;sjqRar+o~rAE!wcM}R2Eu)-3+Vx}UDiLLWXtDWzJ^m^(L9e7gHm{- z4Hfu@*YVZ6!`ZDi=vM@FlHx_LvSRjueluJ#=FqKw?HEf-u8s#DrZxoxeVTqdFQp|% zv5zB*W*9c4N)?m`O@8nXIp87Oj9AlpoR&QqLo%icEh4Y}4oa|gYY)bL>XJofWP`0o z#2l6VOLhCApia)m!~I)##(yK?D-4NhXpmXlE4(H-Q*QoacN!;64eb~{=s`?Gkp5>b zpV+n4!EP2JvGQ9$c})h#3mHH1^PiGab-LpB+Ut_YUuFV$2v1)YfR!xG5Z0CA5ge`d zeir<4KQao%0zqFUsm6JZAtI|;oUWfEks>c0lc>s&>=pbe3c`#1GLI24Bo~%%7uC0s zemNHoghR~uo^O(sL8xikUdGO&@wY^lzx-kg6SAb8RkmT@!IB^>DkY>sr3?k!R-&su z&Cf&!g=|KT*SL*tgy0j%u^wXWXj~v49mm2!seCGSESn{!z#PSvoXXIbw!M#jP_@D| z!QLuN{8C;4XmkPk#yh32)pJ>jF$^27`hzweJ#uLQdfX#F@=$_bEu`glV(+r*yZOLq z-zBco9egOE^~hty!`&)jB549}rxihGT-i#eu!+z~e|Dt(#t_6v?lB%qV4ZBxT&3|c z0Zq4U3r1j68P=r@2)cFocAzf(zC01Txe_ZULh1Hn>~d?sUlJz441_R#LkJ`vmKCY-Lv_M7anh*7YQzSqCz%6}}Lz zIukkq!+z7gDNcKQZw94Hx}sqiNha@pZgxO%$`{KpVnWXF&B+#y3_VLopTSOP;k9tb z8vIE9!PHM{#4RB`C2JWfs9VCQ#45>ZpA1xk8te~aK>|d!`Shh08~!B#%bl}%NjZ;L zv<++&l93i0V2m7G0CVr0&;FwnWyT$TZmP=~{@3Kf=O=?fE7l`)KGiAV3H2|;N;|=0 zhjzcUjbZlpQW`ZerWzPj5sdM-rMBz5-LpRGgM>nsN|W*9D{dkVxP(FkmdsC1S6Ab@ zk0@!x5kP(yT3Casw9bi8^>jQH>mXvcOHEDQ04)wXbdrZP1# zn@vO!A%%%PUc=T_@k&JT@}B1Toz7R7x5*C$8Faqe2k=&w$em@znnj_?BKoF^cgd{7YXBVnsjTOz{#E7 zi2Kajex2EYebQl~q)$P98r>OAi`7`vn?oS0lwe?)O_LYrPtL?JugTy%S<>@!cX3hPeN)_|n8{-f@yc zZMmfMY)Exusfv0Mh@>ixesk$=0ENE+l|v}VUb?dd>!ON#NR)nm<}3jcTu|rCL=Ux~ zu_aH&FH5SaUs?K{Gqv$9%K0j+;%qg)TI-?6F9Lm?`~y8{*abLySW1I4yVTc9y2&MY zU#MPV;0P6a2U?bVS>eNz#!XU`2htb9R(%j->65pl>|v8g(5<*A(SH z#*t)gYhnf$r7V!#yhq=Ra@3sXD9OFKmK?n+s&rhomB?Y&fm+P-A~g5f~{P8iV5$e*H;~dgd=;$N<=biukdFv_=h4_ z;+@IB=*qjp(jk7FL(0lPndT9Bb*d7N1*YJ}WO>Pg?!~Vnt%l=f$^^_(JVFD%r!G^{ zGj`1n>g$5e)XT(iP}oggAXqA$VsPU;gSe?5PC55jtivW4{6N5YjSSf|w&-9u$X?#O z1cebi;d(U(>QUx{#pUa1^@af;F+`a9)d$lmJ?EQrj(H^i&2saH^lrK>AIfl?V!J#P z_d%#$+15!;mF)!2*U_ zf#58s8)2-vSm0PmfNsZ+z$g#s{w~)Mq4yTdY?m4fHv~jnALuCM#F;z6v**RO)_opG z*-o!fG$%|ZQaKZ|Ghlcjj8~;h%>FE^`4uv{EM}m{S1y<&QUP*Yj}7Z-A+x{z-z$Pw z(@HoosB>3l?2Ck&jGQz10UYcfD}MfoS)}q8G8w%;kiJ*m&C$)my=Um*iJ*qa0kZKeqaD z5wbQdbCv`pO$7VT9FwzVDHV0@b489~mmiZG4dfdNrU3hIgA;febz?$^^~3!*wLrvJ z$lC{9X=kw+B##1emh2zI)%OPcjOHsdI!ercnNLy(RiT>+_G-A6`osd7Sn%l{-6pHU z<0nJv1t`BUf&K+Aq!w(Z%`1tTu`Gk1Ttlp1%fE2}7P71wawxo!y=9gVmV`=>5QKV` zt{s0n(1i{yT1}9aw$g7|C)91B@Z`vfpJgZ|E?(_I$p_pu6v*tw2VRU1s2iz%A5Av# zYAT$Oq}GT(*5%SZMM2&~U?a`l*~SWHkIcR9`2Ar{ z?y8u)m+FC1FF_)*{Ww6Ecq7Hu(W{05HO`}d+lOXXuqS|A(u_y_rw&Mhn^na3t&1Jo zS%=OJ#E>D z8}P?Z){D!pFFsU89MKAU7otv!`*5B`jiJn(on9@Tzte_{=Gp1uqB+`~)0`@QM!V4mTNt67W>1pmF~gagC;LdcEbNKYhW}}rgY}+0f&0|_5shCKS`+*usM?# z7iSr)2*xE?`7$s6Tud04am2H@U^1GwP!Kz8O_I_oW!U83RY39PttQKH3trY;#Vy*EC1762U4o`Os|8ZCm0 z^&asf=|*()F|&U-V&C(Q&pbgk4qQi33hO1WiXmW`rayr@su^vDGD#}2=)ndUOF!n^ zoE7nz1c^JJ@8tiw_N8iC=MA%h>BCYMPqL4r9n3YLqYhgosT*#-qd=GrS*S;s*9ePi zPCebZ@s!rn9JDfGPBp#ThuIzjeUgjl#*)O4Ma+e#(Dd@dT+>(vfVr^fB zI(Ip;kQS6NP7+c$p>TGwo-iuI+L}uiIG%((Sq%_yH^7n zX3Eb{dodv5Mr$Eg z9fiE{a_ZaiRpRy#{!{+`ZRib~ltMwK#ZFpDt45XU_bVck+gd0wTYzVAK#d536Jgxe zfgr-#bc%>O-XcZ+bf7DVMFp~=vD=sd z*DaNAP~K&?j{KO*k(gWJp1TT_1jdbST5}EmYllTFsYTOvO-+(nr1elU`;zYony6c; zowexZ*iO5#kz=P?kN1tc;>zKzhL=Dl{1{=aDoK(zxx8eyY_VtFVHlduelmzNjLd%q zRr7q97Zf@>g4Wv^bbu3hL=@_5{aNL-qu$v^6&4#PT)n=Woun zEJ<=A-Gqu>G_Fy6s^W@|laaH;}b7u~`6#66EhMo{b8>rZY1P zqi7Y?ri^v8Y7WSuQ0=QVDW5e2F|np)sfz!@)i*W?!|=SWZQHhO+qP}nwr$(Cz1P~d zZNK;V_?(~6X=jpV(p2Ch6@=f-+vi>mLhtL}?HAql%^gYk}`5Vt!Cx?OmL4O{Yd3Jb19K(SB=2&{WHRRjl^=(Cb}5rfoZdTan11(Tv$7C7_Do{Z>+UgV9i5pUI9V--x8fe1(3 z-PcBTIp3)1SJp=RItz*pZe0sCKW=>QOci)6h<Y(xK=R zHORPdq^%_Rpcj8toF8otQ4gv{!TIWed&d@^odMm~@4wBx$k!+S#X})l%vH*+!t!uZ zlSh|=$&Si|i%wK}?~Of1PcOALzp**Jdv_c5pl)YXoOtEYVu1j z0l<9`+I(tW4+qQe0T&D{)ONGY3ROfYTM$y|e`b}0F2rvY(@5ddr3Uxat`Ne&dNPv0 ze=TDi9FciCL$qr%>tojb+T*LJo`Pgye9V7&gPV7I28l|np&?Tk8n4r-q1J#0WgAAe z9*K;}ua?uvj<)$f*$2_FVrnP>a9^|+?`YmhZ}$Kt$Zz2-Lnr=!imjPrhM|Ej>5i3#09J(ZYB7*Qlx|${Ib~PA$l-SUNTe&}lE^~Y<;k)L zZt6~*(~ucd7XZMEo%%C&heJn`nF#JhyE670RooJ*KS<~Lo&{dD8%I7a{FT3DXo~=e z;+aSm0=5%m#lxfC4N10&XYhyc{B14q9Rq01KK*JPjkG=~lh7#jCnhwPw|2~ypF63x zbV8pU*n(aUE8r!7D0ts>M__hHEsu;$VOeX2^?(1GYX0q5tUqT+*-~CgG&-0GCn|(W zHg(N{nAaFtU%n9z+m|`-+>y%L!^WeJB1O4pRX@3G0!OEK@T!K5^h>q%<2p1g%V+P^ zn@D*soWbsVD_H?~(Zgh8!-3$ms_NDF8le|)4BLEo<*u`J4+<`PMlJu9cmvjMfb3d# z-lnB%=Ttx^oXTuD%N}m|CdS>hl(ME^72RG$==S{N)yM@?!djm%BTvHDhH$4&E?HmO z9z$zmlHR+BdW@M;&|_~;86ZFZTJ48ssM9$3@r;f@#{&s99y{$yn{J0$LL=V+t%>wt z5LxQJo<5 zPhtdu%-a4t?v|Pzjo`{F)D+8EVR7=aRxYJ;3~3bPAky?l#_3%Zt%DOtOAQePx+;@+ z5c-k86`0sehWZ+;IuI4USw_YR>w?c%_jENuxYci34E8?8K^@U|jNglKlkyTyv)dTQ zi%P19EINi5MZU1ICsn_atY)|yv+7)SgGt`|vGUg9T;5%r|yT73Ii zl0lR+LamFch{&#NsUsv!XE;cRbfsMLAlD!pfhmwLB}y$$9Ti zlcxjiPQELs^0)sc8%G+xSz(!nl1B*jB_YQ)S!P&1VtLqWL+y(A5iv|qR;3}f7=-Vbwqx5+YQ}zu1EBse0?z67;oRHJ%!_Qo z3~Heo*36oWF!Q8BjKZUG0DU-7RCASLKZhpc6D;qZ;Xr>;`zOt62BM zJv)RQ^ch4f;n^gieOg`?i~L}}8=pLjNLU1cRBQxP_oX2IAGomHNV_;GjEW<5&xonU zg)uqp#i@MKgtY2Xq)L~zWNfGM-VM>+|B?;o2Ld<;RI@7B<_Xx-aL?QoarHcD>D9Wi zVQA@&OIM@2W??TUfQ5<^=S|1cc?9ceOF|0cPBVgD=yVYNf;?c}%RbtH$hAb1#t4#*Sk%8J3Z9W%Rgf7+JAde?JbuatwI zZw5n-nlpUIwp1iKB!al<6QCW4Rt#GoPONJfLi}iKZZN8z=u()hyApnf z?{o)s@8mXoJ3mm^eK~?8e$`KeCe!MT85uQWrafod(-K{&WrDK$eX#3>%Dv%_w#+Q! zf^`}4Y8v4tKMV%MKa*2_6_d102z;nfIPFsX{rr2|OK}>_@Ic&qeDQG*VVN$HxSSfN z?X8YDl4=en!Ms%ZX4O-?<`9Sx(@o&E)p7AIK3dqKpfF;$x1L0UI}FyVAr@kJT0DDV z*?Y~gt|=)#&c-N-RgsW2+KsA9$ifX$ryXh3P_8b^A}D;@%~j`XQ2f+`^&Tn12S>J^ z#3fxKZ*t3OX}5c*UH~kiy97|GvMpIi8U`s^D`LB9OaS4aZ+dA1;XZ^`KwVm)pHpdL zg(q{DRya5QM&-?L?dL||as&6Fr!g_t_eGoWy@5g+p!oNpx}e6is_Q5&?ak#K&)$U# zK{&Xb7I{n9(5^fDHEX54Q1}X6l6*6d;b=^I$-$9q+@D#D<2=6pGby$lAP$wW+MPb~DCiwh^#FQ5%Q$SBw zT1kvDX^FV-V_*kOb43Z=9ZVH;5+~F)BLll`CfJ2)}>TFyJlmb zT=tD;7$g2l5u6=E?$zW26f3w`ay3>glBFOV%`SxIuGT&90MTmge~+#ulK9}$6u$cOw~`+ z*b2LFw$(@XP@+U~ESmIzEa(42!s~UYx>%2K_yTZZ9s?d=!DBwDr^G#7%^ri>ElS?+gB?XrN%p0;a~v?ujRFBK)tn2zzsLio>5 zz`4jV7un)GIy#4^fw)=0awZsTCN=L}W*kH1ZNKHi%{U@lLvE-NlmW#{5IWmH3XA^H z0-c89>>v2D4O0h)XOJ)x$~^`!bK53qxzG^P0rZW6GP*&b7~k+*yp*6Qs{5GpUU_m{ zUG6L7Pp3J?`7ooP+dbD(Tg#t@k~hJIuh!8Om zrb2=SyCQ$8;1ZhzkgByP>yqe1f;+Vjg^IfgpGXuha-2Rho~@Ds^%^!?T=qpFEchTG zMOcv2`>KP@45$F!Vd@IgEvS#nKy=7qdBEd_EnF=<^o2q$fP-?ur~wie2%D~Iun2(M zq!0v|SzyVNkL#i>kAv6{HU8bM?LAVukYF>nd6oHheIpn6OXrRsa6NcEdKE|q(0xc7 zQ;gb(_nlU2EzJ$)Tn%Lj&5y>X#OtH_1zd;qblwcp*H;k!=L0>IHgY2qOme=QZqtS1 zH+IdkngO6S2h5>$2Dl>sPEty?v{+@BD)%TQ-DUmkOHK%5DEy6$ zp`8|i5|QWW%{Rr{Z|3_@fR4{Tqx&Ebe4ikjOZVU-gnX)>`?nj2xYp#)Hz9{%@{p?CKGL9L( z8`DY)g0EMp^Jhb2`EL4Lb1_(f7a<037r_4Qiba8C^=sW_Za)IrJ^@Ml3@BnrlLyp?t_jd;O>8&atOQ`WEOT*Gb$5Jq>AL(b2lEzS>UbQ|>pI9}H`5oqakj1z<4PyoZk##9ZA=`b)ZAW{65NDNRj>Js zraYmWH-TtfnexSEM)3~0l|BI1lu$X;cJuaNxJFrBs=)(vKTg)d-H@bl+U)}?K(8Co z&YduFh8~U&7&S9m@kB?jft=V1TBHg|AY5v-BXLLhE<6MOD=L?DOKdpR zfr_Pg?V-fV#+T`;EP~S>e>e7{cNs$(IW@yd1_^5nQIREPMvt_5Gja9FmGs&%Xy)z> z^pK~IMnd5Pu0;SpFuhtEZQTR?_&OUx6=Uzh{Z$ZG+!*vo>}c@&QFx%964+DrSMCwh z*d^8nNNa9V`vX)v>rY6N#W8&VDIj5aSC_G&<5{2-a>mZO8HzA^rY~EbS&WF`!1dQJ z3YoTq3{*~P)m0H*&we!$c%{SNt|D=!n`m*8fj&`i1K^)bWa(~B^QnmX_sa7IgO?CGrJ{QfpGdLB`umbm~X$#H$)c zn}zAnXH*Y~Q$bIluD~668NbBO)C$B;+!xJ)&gX4vz*G;PqZ*I*ijb13pfqu^fkTav zqZD|};(zD+bQMeM3lew?OV%~_*&k^PHXj?M#6jft9wPIz3LFqqyC@JW3XN|jaQ;op zklv&1VrqgD3CrbU*v)wzIX=TBE0K}%VU@a!$R6a6{icv8q27$xmq4pXwJkmMCc&;H zsx@3q!)kQ{Y(*;9%GxL4xRST}En*NpYqj>dF+?bg7MGMiIj01|G54GF{px&GlYZ)e zukJ;AlS98@cAXbTd($pkoT8>%kTNyW*F?sIDw?6cuE9Bxbb>{;7FD(~F*MA3I-pbJ zq4%7KLU=%SU5eT$irHm$9NSfnbZUBG07WVveUgfr`vgaLMP+D~e^ou*8nw4Wic)LLAj6E$1>|5lfV{J*uOof1+Rd$*a=&w5k*(h0 zXWw;uCWd?;XP|w#k$-W~GT?pP##PUdZY~2;#q{h+&^vCMWh*LqTa^CDZ%-cdq$JZcrQ%fD<&9^Dh;MR0gD=eupFmk>;Z@Ka zKUahPt7TNt19tH3THln}Hf2zrNWH^01%3{`; z&Y-Yqg}P;7$B1}cw06+RQ{NjDH)sP=*2M}@dg~MRD*XF$U2sQe*yvwow7fXvpiG9H zAuI>c;QviRH1(BS))cEHedA_1+P+0+iOp(4<sauCI*jvl<8+>jgl0!+D(kg*$Aw;J za^nwKcKruP_ahJeo_2iSY7R`j`l2jk5GpsGC&mu2dsX!MseggW+P(-#6QEhAdjGa) z79;yL5V9L6=Hn||Z-BQP2wPK{G$tSYi_*M0uleoL!Jggrf8zh9>?x*}cgrEg$*a* z3d_qC^GJ9S^+|Mx_J$)rY~}M5ZUl)0O7e{TaCdf}XntMR6>%q&=In&DTuh^jw`%22 z$=aELRtG`Q(rODLJb+VA#!Q%YCs9x_M{@>p#zPGfy{Vi`hl_gL6d@X_ z3yrlOO9>j=yj$0FGaq^DKNybQZO!NVoxOt12iTUsHTYpw1+jeAkWCAD0h%*X&~$OW zsZS``g9ur9J|*8xN-#X zRioEsJNt0q!7C>56AmjMl{=tsMnT~jpQpS#!RaEvL#LjT+{h>WBQmcR4&-Q|D0=O? zdU)|1ax5D3Hf`_Va;S2Kqnn4-vOs6~_G8Tr@2qVGlX@H&+t_7%;L*06Kl3tlIJ9_J zZm*KF<*14-ONIGbW2N;<`H_AofX{WFy0$wimFPBtFhbv!7VCJ4Ykn1rQWP_ym@Rj$ z|0gzBidxpPvB&@%y}Ug)`W=R(Xa%zn@tB-AUWcY zp5FuuE=Dqg`9$hmE1q1&E6(j~#W*+N$2(8nvRmP1{M^qK^*)vMDK?i^8#8*i48=L_ zJAM%T)%QF0jbXOf|K6~qp7wN=dJ;goq=H1e(&hiJV3!G8g6X?o-oD}DTcVKjDFOdn z>`g-;Z&dN{I(UhQd1+D=F+9Ta*k?|dB=<1PYjs135A87oS`e%K`oN?nqc?Ufq@Tcb z1wpI^X97$^S%bUH1B3t;gw!H1nfif8R5a2?xw%cb1+ZMbe zmowkm7z@}hZ1Ih|V?$v9Ip&XC(kJ6_^pFht5^h=Ozjl^2ZkS+C9Fu&EJZ zQ(@K&Bm}Poa?oOb2(-?t4gz?p_FX1K!e)pN#Z-l6uX4&Xub87o_XjBT%eL=OH^e9K z)6tFSmF5a-I41C`<>9s~DSVSuY(oMY`l^}8g;K%C95mkLvhT_j7su%ACVNlGeI%~0 z{4oraMw#R{@YM_G1I6z}Rva*&`M#@*!)~Y2(R}7KVXg;vS#I`zi`Rd-$UBp_rxTi% zeOjX=T0jo7%4ft*E6>AN<5jVF^eGhM{*zPr63!4E3`iGz*?;7BfDkOS7OfMpWu@9A zb@UK>7pzZ93+<1_HFxc9t{%Z6T*ugN{bK<}InfB?cV?HbP@$mvFpH3{Po{2sBW-;C zO8bBS+<38ga7Gc~Hnht}y12>flR@Gf27yT_{w2!u=R_Bkns#alo{jmsDx#!D0JLJK zVhAiF@wbGYd-kn4WO6xsO*1i?v^LrSx#cTxX^mGAls=P=7r9g5q?s`Jj`Y|KB6$S( zl!z)SohZ=O)D?WV>c+i#UOnS}&jeFGY1$&%)21BE1&-jFZHpVWGNpAWNW)hSBn%x$ zI9WF=$-wOAKo4&@7MBi?O%OP;r?Cac8wBCL9-mC*b4IhW>kX#W{YGi19NN$Lo)heA zZ)md14b-0xl>~{Fv8wXoEX68`2#J-_GH5Xb>6Hlu7U$gyNt88AcI-`ux5P6zuSyNw z5h=q%x(5fgvR#}%Y0mS4*0o$*T^M`J?v)A%Gl2$z9`Ch#Yy60eb?WeY4X+ZqIL$T$ z^)X7|XJ{3&hHS~$uN!YSE< zdyaCB8W9sFB9K6m`3IkU*)YLFYst_f;AJ+~)5K~U@4kg6gmDDQQoAEpnMWBmgmu@f zt#q&2HbqYL#fvQMrxeN4;gekVWyBTOpE~C zcAzStSi>{TZ7-$FL3f7yk3+gg8&!HPlh8jJPHgx<_P}Q?t5BAt2w9=uTQBAqs-d8qJ%@FVqiGC~$%GKV(ijjw*dDXhzcAx?z z83p0_=Hospp!{KU^d3T$&Hcteu`*?xb_H36_P)&m-}icpJ!=+kn%p06Mm_=FFYrQ! zFsgQ>fo$Gyq!8bcg(}@B0q+UcErX-v@auEyX-Cq{;4YF#iX@fjZy_WJxuLqAyjSjf zaAMSw5;7KE-fjTU{VZ$A`z4LwEm-2F%EhhZ#mv_O#6Au801HFOuVcRtHpYQTJQct# zv8oUFiDu1>yp<7Mphh6Nvkix;0RgAjsO2uML!dO(ohA9El8|OJ*aX?Dj>7_KA3K?{ zw%PobFs5gcl63gZy^``xMI}?cvV42q6tkZLVn71fI%lMdXob- zwV2h9>`KX_H!y#(RTBRj4Ik&0W3uE`23V4ofP~`Dk+L z5s-!`YwfEk?Lv$--0Ec>K?b&ZocPb4ieRqjeJ~$76?S(d>A)hggx90o9YH(3kj!-| z9CtlO)4mw5CrH_+croJoz5mK+$E_~{eb>q_P4?c0!)<`X{^&`favX}KK7>L>`b2M2 zh=8Y8TE_}Yx>Q;{{@SU;z>Q4oWb1Fd<0IFMqGkfYsvrUxSF?b|mTpmL|5wp4EQ=J# ztnU!kX8gkxG9J~-vFKH~{K7|iW|`RZ4emgPRV-7{7JC`Vrsv?db>8$>vN*w(Hy9m_ z{m`V(jV~xWutz!WgTfDA_atVPT`{kX3()$Esi)OuxX-XKyUq_RsHXlmHdZ#KgkfuV zD2&4QrpWY3(iOd7s!6Li%(Zz`WXUyLbP|pEfrrU z%4QiAA@2&7zGI%Q31Wbp-t~v1!TPZa4*62%&F%j`Ar@A{@Jl0Mzz9l7fc@s{i9#KF)Vl+7Up%f@DWI7gCHAX0} zy#NA{;!#~Qh{yVnTm|BM;e8LmT@I>7mKoaHfICJWEQG8At^|y8Z;Swb{JZC`4MX$i zi&M-3HXm3<|IVHUqm20#TJWMh2ZjXhExFU^Bs}M=w%zEhoB*&%d_NJ+|M&D!G71Y^ zu&zLnuQH5MUQbdJiSp%Ev?@J@=oO|$`bK}!a{&jjucG~fiaC7PROIobj9V8o+$LNs z>TjRf&|CrZndu&S+^4@rr~y31MDj;_3)B~M!BFA2;w>GG7J$he$yz+neK2A4pEC*h z(gp$FQ%;w*pxXKtBh^He5JVT?#C0Jnbx9?olS0M>ah&haiter^GfnVdCfUC7w6FmK z2Yudoj#Efjnxmt~`KqX1zy1fCxN8UpFhf3~d961eT_d)!3($5exGa#3w`7R>1PR^k zdYz8We!q#KtOFKWhP084q>uJq-(P^9$O&DQPS**A#vjQSljOUY$9Iz}wR-%{2sj8D z%{+KM$tJ$c>0c)^l|(8mxCHpKWq{J?JNL$P$iqC8Mv zXmi{+f8Dq-$afquu>_m6(jQ9sV=fBB-DY~p(X+d_#gAT5^5!&MZ#Ho(!=~vS>Hy%$ zEA8CH@_bAV4SP|QUf_C3-_TbdNA4RghqU%BLFmdpayK3uT2;5*!2G0Yn|-cu8P!Sr zjyoj)EFn@v|2$D6}4{(OZzxc$&~S&6?{d&H?``XJ9Ch( zrNBxCRxn#%dj}|Qgm>4_R%$v+Nvy}*#9D*{k9o`th&eO{RXJy%Ik|}+PJ?w7Fnc9Z zyRGg%TdzwJAh1#~Fg1`yOJ|2<@7A3y^o|=VLkx3}OH{w-wYme;f!1HMhKyiiWZU|Y zcLh;oEVmXhaOedF?#VkU8=xE*cq>=ZGsQ*TGRU}-QGss(j!j33sp)!igN~=If>XM8 zHUpoL)Q)0y0Viop@toky2PN$n#sDQ-2mZ&;$U26&ou=8vnJRf?+InRsPqN&xCjC8B zFfZnj7WhYIykxZ+FA$GqwOfm}wQ8ezH2R7%-_zm;LpS$_?0Wjrfj2{%mvi)hAce!Ux!JrXPA-EYC z*gkb5LXfsg2wlLq$Z=ZjrJYMsM#9&rcVb{D)6Ke+NXiYIDj_H0FB?|$AC!DjyP@8_ zjm)Cj7xb1pg3sB(a@U!opxMAMe36h`IGnbi?H_j5uk)4VA zYJp7yRaVr%p%@=soUR?lg9kY0^%bwkrgjmVU0)TldIpKgL|;$BxA*BXAF6m)T5Eeb zd>4+0496n6Jg_K$vIsI)61V zfca3G9NO8hdz+TaOuk$OrtmKt8oQ{0PL$r|IK25hZh+7*B z^ubF%yHNs(*)wxplEa<%M6)|=^dImh+g89bY^OHx!EF|!cPABP)0mI;km<2nSLm3D zWs|t9!7%zWn&KRD#wgyDLqezGinK3B5=SbrZ3j#hl5q^Hi<#}EGC^?gFIxs6{i8Yo zMnu*|{AXq8PA8V2MJeK_s!TlaYv zpcaYZki*Y}D%|`!AuKEMH@1_R7)^hmTU4+!vLg?F>Xn0i12c4a1(ISkOlpXj}@lyMd(n@4Ji>ELeIipe+>0;n}?wZSz zXm_Rnml5RhnW^Q(i!(qQ6CmD|+J)QWP$D1?Q#-I096C`4sFl(972$7CAtQsGI5!L&L0n90{?WfBXc4}d`U7-HPz2Kk6d+mazxIfGqf7eQ4Ew4i<3vflx8os5)Bd|N+O zImz0FPE#dJh1V^@yk>b18b(af{{U9ZT269a9_&5t=DSX>O!!_9o8>X zwho?iv-RIx<0aYxgX*P|G*hJi$;uoeY5rW4?JGTxO^-@q8`?(vJvgBa`6cg&iTRQg zRe2RzA&x?EJw3+gCTQ#+XfsMcE($IS@BG-hsX(xcD~*jO+^*@!)8pZ1QF zL+5tmj$PUc38#K3BrF>dVnaZWbJ|rg zRsmC+M_b+fj#>RbNVJF>D3V76x;pg;Z4{w4R!4zFw0i}&g)-pYxzB5 z{W}3`HY&j1=qw$2w4t*>sG%%4^NSc%E;#(%peEb3t%AfEwb10T7hStgEm!e z*+ZO_iv{l?^X|<-05~xj?t>Pm_G8O_kLgXK*eFU)?N=kleTHqnC)}U5_tb*QzY1@Q zZ&@FQzO$K4T5vqt{Filut;Y)$J5SS1;~TM!tU5|vOj!$U+QE_@x)_YYKUeDDt441K zVq9N)GXo?)=t9@Qpm1A=Y)_I*Bf?*zo}hFo`W)J2sZr`n^i}k2vv#4+76Oc#bcVk@ zR@nT;5*Z6qr5aJ@EJ}XDF^9DqSkVIUFSB`; zLxL@;xw$FW9EL%bHBLss2xGSMg-nZMPW{%aApjJHzN-=3M0*bA)rY)3ggNDhU1!Jh zTO3c=JmH!A;)9^EJl{imbNRy~^>A8MG~7BbrZ3oZonZIP)#Y}8C#{n+(CmQ7EHojd z<0ICAB2ylg(e-My_wSBt*NT0xtV+VV-i zs`xbW!QTdtm{ZU|Q#FQ%)eVR~TQ3#uT28O5YiAH%&x5{Stu^qk+s`DE3T1hJ;PEx( zLRl!Oe|~$-ef~D+0t;<@0~ohD-l6|{Hvlp?DH*R}7s_8TvAXM`aXbK@ zxt5}eY<&`lKu$lKQv`KHZdTKIBM!uR`FY-3lN~-h>M~hz4aq0D9^v+I?e$enJivYp zM*SkiN}H9@-R=qk5aJjGjHSyBXbg`fKifSR+f^I=&! z+NUSo!(jT?a1}O4r)xeUL zQt$5B-E3M=#L1ajcAG0Xc!Wo`gttL=1)z6A<+)MOdrlWKET`q*bd1(10-&~cCoqpYM?`cM1&5r2tf#_aBe zz@KR=OoX+e9(lTzvax^7)MLI?RTkNN7C5f~pB_L)s!4Y?;UF(uncYFqhqm3lZ?YS)->Vep3T*qk5(+| zu0AHzF0~RibyCrO22Z#=Nwq~t;bX^DXE@CBeAd`hr^i~{;*4B)TtS~sC4dH|7J&v<-ci{TA%;Wnv1q*NkV7b#a zIFqb0Qy^^~>zOd7^&4mUn*gor|{HlOyo7d6ssyr$vl%jnQ1ZwD&OYXUCG7d_;nhj<0d z8?4yUj52S^iLjXp!g0KgSy-X2imXOX36!6>b6+w2lOCg9Y#=jt^P1QU{dyZ6{OU|R zoo8x9*^0a7ncWYlJ14=o{7qODcsFS0Ar_B>-iKE)g|MVR-muIp9mft_VO`2F*ui!v%Ur2!X*-~qQ%Nn1lG&UY z)KAzBJM`gq+g1ZI10!KCqpydNfr8dG6csxNrX) z+SJZtV2L{Cb}L5XUyf%=t^qO_6`keS)BM#kwF>@j$n-EB-E+SL=ABs+xHr|?CDCvH zD*DIi=L03M96;^fCtpF3Yt)#Oq=EmH+<^~3nc(r>P1Y-ZRq&(CNtW5en{i-bb?v1m z0^DpA5{~e9V0hFTsMFm@@|%-GMp0i4*2UIVPZpDL$W*sZsAjwhw}F(X?bj>qy_sTd zqAl8lG(X?Ry<=$$tnufTzI|BD*=3jx>;@QYiG|cKdlnO_oK{VckPw)G6b22$DiGs| z-4MJu+w7)fr50{j&eOm8DcNifv&}+ZKa0WIt=}Qw+6GRmHrma%r{A+!%h;VcP8&dYtX{T%_P%Aa<-5*@4*^*YW(!1&EpCXjQyvf^BZLsC$@S93@ zA|9xNYEJ~#GOpy>nbK*Itm8M&SbaW))R zop`1kkQ*^me!S>ps5VS$j!n!?a7;ttwAFOGl&yZ?GiN}re~8i10k`lWxW~~raJgW2 z?LM752qOqIAea<^;~t%cM|lodtZeH;Nu5Gf4;|lb7p6s_W&8SiI2*hV_k@0@kKp3b z0m8WtlJq8_wBIFCvux{pvflE2ob*qH8uvtu^tM!)=p*EAE zS9yNjLF&>?d;|HC(x{9oHTFv=(guf&ow}rJ@hb`Cz3b3V(eOBG#BPIeLdQ_(W9hpu zhRN=BOhq_Rxx;jU23gO;rj0AGChZ)6e^CNtGmD(Um6Fc(o7%XZh|}}dG%HAuSc}+H zv43OW=@52FqG4)!_v3jA%N2M? zK`dmIwGcpYKspKvVqPza)LjIt1-U6i4Sdef6tQpzM*dn{wR*;61CY+DLfxmB(enC} zCrn4RL_r{VN1!GQ7IN3ZI6FFIdVbm{M+){Uw{wj_^QKLKf3in(goCFIJLtk8L`U!| z!tm}7r9$B9xkaM8#+-Y(lt1WmmfZ{;LsJj77|mf=*YHK9`~w;eFwQlZK{6&uh{x`f z8fEmX&*F%t>A2^dZSD*5G7*Qh@Y5v?TK{l$y8`YA4Fu6?V!{MH6-eylBz#lN z);rdXIz!TcM|3)5ZFiJ9a|lgD4FXq0?F4fc(Q+Pm!d%H!+@Z5>?;I$teDa$@R%eNS z(WE*M%z&70D}1)LGtTVNM_n`_`+9X(ZyUH6sWV(TReoGv|G9c(4un=7NlbP^mN(g= z-t~LRwI_Y}6Ev1>r>BwZrsb!7LKHOlPBpEH6oy2)hrlvuWWkMap)j=s*Y&=R;>PUc zRA;nhmXy3CVail30%!{^A3V`C-Lex zG+HGksnWXAinpW%t!h38bTe;Lz-oU@#>|6&fd%a?yb?<3G#C=hXY;#BQE$>RKh2)_ z0g*N(P`SD0@2L3HPFW>6DTSG2u$}>b~X1GVFK#9mvdUA^y z3sA-hgvfe^YIC@h-90)|mJLN&Lb?U zm6@BLtAT@3IpqEPpzhS9ZQ``^-(`z8L6C_3N#15xG;rbALdIYW!_a2VbqP#_MbY`$ z=uaa&_(3>KL&LI%OQaIzuE?sk7ooCoe>##sxPS6v;Nc(*cvp8v`(st%K?YKnh)H^(o#l$|h3udif!SqVf1>JNlTMx+{c^ANo!KQJ}O9&J@pE3Y=L# z>8t$h=lxxgEF{t7emcYuB{c91$8k8BT$uVr!|)u9vn<30;nI$nGw)XZn3D&^D69e? zSz-|gUK|0}zSmq+TF?>fJg5MEMmHwwh{u!=qKO@4{f@-L-ll(=D7~ENIV)gN>~aH0 z78T0g-voYX2U@RPf#**nAI#B|%1`Sa;1Q%|LAhv#&MOFTf* zZDnB-xH3|!!+NRyl$}}xwSD>DTPhvnfV{QovCq_Hu*Z2|z+wg?oQ4qWeC#nBpA6i& z+jNmWc&B6gL9J>$>t(E;ysoNZ_e5bi!l!9{(v>z~W9UK!G%GGB4vQ z;)%{4XonjeU#_G2u^LVt|1yEJZGzPw5fh3fXTspR7boXhsZihsh{ZS;r1&%3&<5|& z6WDDBZGz0IcW07s?uTnshmcOg<+4G zYmcvndiTx+J29g-Ft5=3O;%ZLakONm=>n76K25@sox%7$416bh*L=`(UTAIc55g=E~F?ZN5wj6FmT7L0o zu46oUMR!$`R<9oAn}K}Dtfs<-VHRS6h8c_lLOluaabq8lI#0Kpip3ovcJ~*Ilmr0k{;Z%9`{Keuzv9l3rPxuRdsqE6mhjuEZYfQ3gRAizGbTY3Oe62aQ4iAjmEwQnBt_4R zws{VC-9G)B&!GIM2@`g%h@3fxM#7GNz+&av0;YFK!kI15W zfroLt{%YMO-rn4w8AQc#^|!@LHHeH~2#Geqig%q8>UW~E4!gNARnd{?8KEBycOhw39vs1DtB z@KpUzms~#&yy{RMmf^WFaS6N5s(kt5Dk31}t1TFb0TYMeuMnDQA<&9D8>^7$TJ$wM zJ*eds-_w8HH7B(D{s)TtRG0HRqI(=8-ct}ELvRjRn;OX6c~!=}dl7HH_SQ7pgZ+FD zU2vcX;Hz?qIN#UTB=YA9*10nc^{}f20?H?;hsfwpF;#BUWi0z8>t`e0)uh&i#lO0x zhV0)|{A~cgnS$hnoVycbzWc9u^3EWkb9wJ7LbV$*6);T1OW=%-&vxQbwOj4XQhWk`x?du3m=LJ3lNv<-IzVd9=pm)`9+klLS!WC9-8y zaE=B?Itkq@dnKUx3`;XNovMqN_8fybU|&PUQ$eMn!BC^u$3&7?_g<$OfLAD?$@P=6 z$z>lx1+qis?c(TNQN#clA6Jq8DaAKC=V2t#y1#nL9O;vniN7Ugi@cZ(G0jRTg#56ecVV-8 zN{ypfj7tmh;-|z)m46rx)kD&i&wG=~AU5!3(lBEck4Y4YnC2x2XzKfhlcsDfc7wXB zS!t$VKHGPZK8o{guo75oAO*$O@Fr9J`ChNof(3{^72e|^zKOyJC9I0Z3tuP|D}j#w z+;i-QH!OW|L5ui)DhQW{H;AR;ap>|_bJcE%P=Hv^p#y%L@{Ct;8aem9lDjylYxV6a z`Qt}{5+cpKvCUW@y&O#%ira?Um3938I_P^e0nQn@Pd$>n##WGu1d+`Uk&D8|Yq=h>Z3v`3OL`YF)*CXKRhLlVImq(c4Rd1RIxpQXT= z3fLzItYZr#%uSYM&zLg~k}d!H*vs#?zVsfI$l}GEaGoj|Q1fipTDiLi>b93<4$n#+ zsfex^6bp!QfCS^vuOtK#eeB=sd;ag88SUc$XFknJ@NLoD@t4kZYKpt9{zbM!{7;)W zlf;t4$my9-&d|iNiJLae#hC)b^)&p*!StZgc;zdQaVqprRRGFEz?xU_mD`e{(WPF- zC!3WCHQLMz%VpvN+~WQNCJt2!hR%J4P-jA2&5_sMJ;Rgl4k1soBFNLMX8tQ`)4_1x zzd(vc)w}fKfi1qbd}(!tzPRDv{L}z{K!CqT`-2lumqxHSCa1q!U0*o7ya@}RfNMSc zM3gidFsu*^>w>>t zXHkjjp~ra~k!dI_?CUn5+_0SqP5PCe*giG(Ms+iTDcaM4P&ukO0w)HQp9TbsB(Paw#j0VGS8snxm0xz2OHcq$ zf$>athc8|^{kGrsU{W8H!sIhB=4#6jStAp{FKH)o< zeom6|Ce(R&L%DEIy*vJd1V|bUYP0OUlIQw-3~zf@V!EjU+7PPLW}MUf&(>Gy@`wef z*JDvRcB>-_-j||-K>1SSxi}%zB@<*6(O5X1|2IJJ4@NU2xOXw2=Dy{j{zNQeo3iPX z4j^;h%8ctqb476F+9^-80Yj|*>ZbA(u@fX+fw2<+3_VDpO=-cdg*D9)_SR=fH6vgq z=}Z11A+RW6c0J*33B1+gVgNT$&Zb_@{q^kxP8m$G{ercowq2M1hpPj>0$$^^V;@)m ze6=<(rLqho19W#P^w7^Bxq+I_1_-w2EQX&C^j#cRsOU+BVs~tjdky5 z4|P_7rDrYmWFCY$js}(b-5~$H+TOwJLSJ3|)BiI8AM%knwq}4hAkc%TzRb|})y-!XJ1Kk|PPp#=IV@tXma zZGnoSgH-X;$9B&plhyPDT_)U86PDpYNaj2v*nwkl=l%8SRG@Y-4IllTH7%*bNztVL z2U@Lyte*aqOGEgByf5v`opIR(?+|}86--ju@OooiyNKuk1jOzIsWrq}v>FBtp!@Hz zy&}R#6kF#P4qXCXP`saL-O0_JedA8p0>I;8# zlO)%skS3Z%twPJKF#}*wzsE;Lq16$go)$V!t4&1RJ6{@JEyCub;mf&%cg#fNwa+Qb zw~o`?UZy(VGM64eH_5qh>p|$8G^9&Dl5bWWXWP2l&PF{k%EQ9Qlg%+1W|1JBz$$bx zm-`qk_IY%&hx@V#QkM2!a~=+3*a^4b)3AmT11 zoLz$pkkL13CFfaX8GLf5r>ua6gQ#9Ys&%O>N-T+j8;gCt!;1rQ-SRI?Sx;De+?pPu z`PCz~zuGOXB4kPRQB2AIo4jbutlY;Fc%fb#TT^f!l`+(aypg~8IBT;l(tnnRFWeql zU~y*Fdsz*NIhk7daAG4Ei{(>xI01DI;}Mc-V;Bp3nV-JPYz4ob4PzCTYM~a^@5YHw zg%m=oc}dlXAm%Q8ech#Gh_?ei-n_ar)?gugAN5S702ymd7`+ZW4abT2>p+vf3S6z) zH;PM5_^S&(>6G%5QFS>v`!L#644RfXwH`w$=%KK0UQu@M>}-NhSWpgP5>=(C>;Qf% zMlHj{ckJG()m<#QVKd=gA3WSgmSd6w`Hd?`;=bMJue%>ugC%WY#B3I2-a5lafDN06!9z`F|j&8Pzw_ftQ# zBk40jAk{8-z!G7?v4Gf|e;6psjLK530hs2(SC{cVmOrPguK=L%ucr+sqfIH|7i*_n zQ_I-2eB#!_@;fN*VZDG<4-ro)%mS{nECZ*So)RkyHWkV)yl2y^&K`T3Hzvo)Iu|r? zB?L>rHhmra2{E$X_L@I5r+y24*zcH_M@iDh?{Yq%Y?*@z~Dq1AmLqq%dIuX#8YYFY|Q0Qr$}+(_lFiu~qdpBo&L%(yDg zBW&T0EhuZe!kUAH9jaOuC*l`OOWg&*u5_p|ep%Z@GT~dLFAsG0n$ zYyL*Wp;C*2PBZ6qkO|N(nw6?b&^P27K*F0>q4UdH={Q)Vy0{kjcNDF&-#y~3^`L)6 zl4`MxvHLtszje|D5|tB#>cy!sz~JIQ|Ljzhe@!FsTkKU)ER-6sh5WbzcEHw=Fk-D_ zI9GOh?-Cj%DpI_YV!Ww2mrkP@*2%@GNnZL5rw_26PEG4(h%{N53fi8|EpZU9rzZ^N9gd4s zY}a$I($2fYFrSs3HIx>!7_5@s0#jp3yv+mPgltFe^2@GqtbASJw$9J}53EftP3-&3 z^@}ZNs2BnR0wf}S0RY>=rV(Fxl~qhAzMLmT*KY8J6;6%yo_@-KsYbVgCOl$fyP&4Q zbgir3VAydpnBPWV=; ze%KR4HwbzO?tTxmcLGej22ce>+`0>0BL$mpU7ggLpl~wx-BQ$g69qgmkYXtZ+&WvS zyYtTQmKXp7uOt>j+pS^zO}j12$9_jZWErK2Cwe$@Yg^4WT6TVuY$U%XY4;BgR2-$% z@qQ0{Gk)`oAQH)^5*%0hc7WSG5IChKq()OdfN4D4+PJTapF+tZjdMENx3}~GRE{)( zML7HQDPdzKE+e~==c&Nband0-W$t-pgiGQkz^G2CJS8<#%&WfVrSOt#&6PTv=^{Bl z-zdAxsI)Y0z~zmFj%GSY0ma0`7n?1--OJIKxBI~X_+1GCs+?XE0L<%%Yv68c_zd4s znubdRzC&c7>^M7-_R()89)NzuYs+y~O{+HGm!Q1?vVAAQ7WyXcZXzuAVUphoMxw^& zV!?Sg*tZk(e&k&}tN$f4C-{)8tvf`QexhENr+W%QdN$U4AqC$lyKx#i0j2zG3};Ee z%iXSG=MJlCruNes*n;9Om6w$0m8$;Nt&HgOWxmaLNm_=zf_D^B&;CR-sp~Js=lTru z9RZrVK}3vA`5JUX`*g4uK=`G_45=*2q0#htQeO(eMBrst-)xLwrRTyZFjp3!Tpx=j zrMv`34ZAyQS4Om9pj+U{86VOnjio*?wi<#Lejip-GAqm!#WY!2tpb-ISf4&E6QVKd zyBPMfFM$so?irjoI*CI`VZwGi1kYoD%sgPUm}A2nBWR8wfu}9W)LPb$NerX!t#G-$ z?i@Uib%COS$E$Tp?(m1d+9u#S%L;-M_Y^U>d4D4Q$@rV09s=#!v$`x(SB?`Y+YVOA zxCeuVK!haXb$jb+-JZ{R0u22CSpbM~)Sg$%Bj`nHOG<3O&4%)5%W19d`yMqv{oTv8 z2t?&31-b_mcRYeHM`0=Sq(c1(r;|`HIkO%Vj_^@nw7hPHpT{5z6yni)~S;#x^o=pBny; zo33^rrPoua6wnxRr}&wcT6#O8BX-Z^Zjvsd!d~^r8u56!8cxM~K>pgt#y8uXwH-|M zqNq=6sxhcqx<9<>BF*@7Z)`!^KJipjDS2O$`V+GBp{$dNmf%|}cW~pBbjJ*&^uDixE5cq1`gx`W`vNWs-`+$Mg(-1f_A@XMM_wYaDaZnmtlIG>u?dx>xvhdWa?4u31qw-x7Z|Oi z%SP}3y1^-4Y?{NjpdNfbvfzF8dO(ZJ>)w1co+=(Z@2?VRgyE27uN=(#u8&_U=l_&Z zF9DG;kuAN{`7M)HoTbKArM_&-U-LokwAU8828Ge>Y+br+_E_e6X)p?VKW{3szDRR| zMruMAUh9CzsPuM|7Yi!Q_tWu8O{**b9#oS#RZhinT2eaZjr8A@AXjb-=|Uv&ohP() z5)!q|ly(29%p%~$_3iG<_Mpv}Xengd5yZxJDtuMaEIq768+6MpeLkj$v`xr3%|la}-C4n>Z6$k-1z%RS_vYL0%fcUQ6+(*}SzN8({RaI$znBjWEH1CP!(w4<( z&SQJ5;5&OMBUk$N1+ry1+rHc*%5c@ANhB&(Dr!|!MhQ>S=`+ObjODCJybb$;(=+Zx zAQ%TNCSX}*;YBMnR|Km~O`%S}r#_W~!h`5Rv; z40+HvYF8E*+8WXXW|4%sm^Djijjb)h9Q=w4Y$ut2f;Ryci)&!;#+k!$Pd;9b%2bgdV#l{A=q+>hwG;hjSg&=Oh@^_)F~C|-|6iIzYH8g% zQAcYqqPn|i5vA>EZV!3KG1LaV4H5$ZDQH}E7|i>`Ur+BfDCR=SFm;o|V&T}z1I;pr zzH?^k63Z0_b1&Eg*V)aYVU;i}d}Gc*Lngj(Wg*w7buWQ(_DU*F`EoNAl?W5)8!55h zz1kBGM^iV=2Gz{*Hey0@T4I@}VjJIX9Y;4ZP3>$llnBVi@D~bYZe(+Ga%Ev{3T19& zZ(?c+GBY+HFd%PYY6?6&3NK7$ZfA68F(5ZGH3~0GWo~D5Xfhx(G&VIg3NK7$ZfA68 zGaxVuFHB`_XLM*FGc__XH6S1$ARr1aMrmwxWpW@dMr>hpWkh9TZ)9Z(K0XR_baG{3 zZ3=kWY`SA`Zp{`gob1@Pp4hf++qP}nwr$%^c8nc6*|BYY?>Xn5bL&=pwf;CiZqVp8sdYe@glHeDspi zn!<{5)c7p#O6DZ$|+b|F17==;CDQ z0nlck`&WxF{OkFzrTc$TLPGW)-n2{%8~|EoHUf~f<=kjmS z|EcA_{=b{a)YQY&7sboCi2cDb7MV7fohs1JMX+s%CVdmNt`jpprUIK3~?u6ZSm1goKlw@IZNvs)^wk` z7IEGpwCLK1|Ml^QcC} zZ$I&}RCW7C9`4P#z4*;VQpx~#*));6U@HQ#1Kl*q z541ahl@UG-gBm~W=#WZ+tz>=&dL*D99ybrtV}P-Xx8P5NU%WA$b>EM{=j?8EX5CLE z7ate4WX@`d8}=4t3-PrN8i{T|<(%kcXqLOpEk5t2+E$^Oyp_bIVjq z-z?Se#3CynET|eu<`!I2A-K(N-se#-DT7MrOBq&&m^y_!7SywN=Zx(-zWeTdnHGfN z2OU_Mzfd%>X0XZ$`F&@sbgWc~R8{s++^e3;ev><|y?QCU zT?AQs5uL4o8A7Ho^9}YM%mV3m$@75hZ!#PK{T%vH-uh=bV51k@l<7gN>>wA+GjaYT z(YXYx!V<7Hox%(I{e<2=B&nhUBqt`*8&@;?Qu6Uq zKgv3(LBC@*KlnR7cLin9GHL~xGlGb;FIt27js~8^i@tR+=+Gsk0#^L%;pDo3a@&@iM-KQ%7uHUU%XnM6yCPGUj<^R+D)vi{?K# zL0E*yfqF`p7O*vjhCO&Kf}@|j*h6UAPHa(gg2puMcXTFT>kb#k?)!Fy;QNB^5V?+O zx@Os_>p6B0`g4SkZWx#rqgUG8bx*-HX{{EYXplLE`cl%9$9Y|BU}n|Uew1tTthkKT zQZ=~%ld(apU1;z+2clw@y_SmJ1WCZun@mNO=*=4)75gKZO?ihSa6jwEt{MRO{9Y zEGRRss|U`lNhPhMRX-IC6YT!x9f)Pk3~P?aYANUREOK(}jEgc-6rIS&*-%5DKDbVb zBJAm6m0`l~PFn)o&Gv(^*iOcNgqS6si{MnqrX&LYL4lS`68)~YQuJj(*mJ^%#rxzb zacQGG-u!OTG%^isO=FTA2KvFLI~?(QbJQserJx?N zQx8Vokx%F{Q-=w4|MvW&)x>z{@~?c+2>K`{(a{;4vblldNih(9S&q*E?xef2T7~` z3`S?Vj&fK4Tf*`(*`UZ+9(>7uT zqAQNa;H7SYfJ&)JR8_iOG#>XBADda;vM_C*v*+2 zHxdf$6=9`gr<%bJOWiN^8psrRV($F8tV;}sA{j>Fxyd~})DU!TG`TG?UluN5$vc7=Fg{8pB1r84&Uyvz$rqj zE|i6gYa%{V0d7Pah0jI4i4R0dj_1Ti@;nfpd2iE?E@B2~=JFWnZG>=tr8&_JIiD5p4#8S9Z&cDXnu={#4(OSl0|>CQgmzC}twe;KFNTicf= z;G#J{ZC4}|im;OuT~RKL6oqMLygC9Mz^j*cen`^l-{giS?Ysm%TE7D_u49AW$Hufro!wWl>Hzt&5M0?SL&arVG|&$#84Q!xUow=zq;aJ zHF#rpfWM$cP`&%o=eSEvr$+Wfe98w?7DqRR*=j6k^C&A`==&Q^t3K;CGe$zY{O*|0 zcT4xfibwbsf8Y@One+CW2fgIISCY^Ue81q3)#5V9y2>X3W#w|=)pR$T5D?t3C`1(A zoJ}0Gu34Z`kew0eD@QPJo>giwx%)Y?x+ep%N6GO*cL7pCuy563Vm|z!FMkJ$M7eImagd`-My(3N$_|-Skb8uSmP0Y{ z;GX(Wht?izf*lL$K+x@BPbdu5>PMtoP4aAh_dejYDYh9(l6f9b>zqpZ@u8G3jm zlowF5I1QVtptL?~#Jdp}20iJQarO4UgC}V!sNrSMZN_+n?1E!#=Ntgl+=im?v*>J= z{JIzqTEwA(E1IoQKIW_=!00LN1S`mY@77Oaq@m6t>XPR%fKBtVjniSlfEcrWZ2}W7 zD(Mpa!dT7o-aj+zs;<6CuX0H@YzLSfrx87UJK$Sv$a4k^;i>>Vb*;M>aTB{uh3K`| z(w;HbV{uPC&nD6@M$qFve@#`%Q7c}ni&HhQDRVdB?;BJ6O==@-c`@$v8O{c+*3-0Dm(&+z3WUgBM)Yp6Wmx6tyO(B#- z-oTNj=8c-n-Wr!9@76S=+M7&QK$j<|;IkG=-z!MW*B(#(uZs9>ytv}iH!_q`KMxZ|@?sK#k25-enoO zM!OyC(RVl(9WCZuF%s*lWlh4;a|HAfMOK;+dlw(DwLSHLQ}r00Z1QVboe##4#EZ98 z?T(GT!{XB8-Q+U7h^CJ9JHHys^wx=_5Z$8{POIKD>Tk6RjDD)>E}KvNEvI`1~c`%M|S02YdwcS!1Ei38NZXy!NClz*o2p0o3tsV8dLuY0VsUlTk#$VtzOqCKg|eIWXgKkj z+z)(LPq`tq#I-`HF|Vq%NLQHmjNaTu&!^dFcv(r8U7noiQ*D_tatE({DQw6~27M># z-#$47jsp0|2`;Cx&&!2W;>H``E@>=1>n73eGQ7>bJ?$sk*%ZX|uP@#jEn8*1?OU%v z_L{-NNS>&un)(eD$-wD~(ERW<{(DoT!9vgdfojD-`jaLi24C9*qO6y+8dCtNtVIB} zfo7P*>PV`;#>&B(2jO(b&sTuy#%1bFlZN~1ZL~Me`-@({j z!q?d6x_mr)Vqh4Zmf}a8KAt9e$5L74D`Tf<@$heihZNgFwy9n$`k7U3$|}zHobOFVraMs0FvEOW zwD*_^9gwatl`_YRqPCB!=QYedx&Cf)klbBSv6lQov*XPk)1I* z=~?#5o1sF|bR{QUF4Mq=>Nm1!+Ig8(GfG^4773ZgUz}=W{t6OEbN9OnKD4+WSi0wn zx&bq?xOQh)!$O1kcBoYFd1FEjH@dJ#?;;eYAY z2-OPSF-FY^tk->^PepkD5$RFqK$rTW191)*;;v(h5uOs2ZvkN>K=6_ zxGaIe6+%x6-;h*33e(%?kE~t)e2#UShqRWvBDF#c%Xo;8!&i%_b68rFfxGBE^m7*3 zQWQZ65$pnYniwC0Bn=ei#X;g}e^z4}a=+Ayvkc?oZ2oK5yPPH$acD_{%g1E zG)7_2Hsa2GA&T|-9T>qp?sd$MnboRGmwsAmwnRp|a@O#r83N6!53ALB={d2m9bI1{#f^wfP3W36LuzlG+Ip0DXVw3vh-Rufk3gc21{T#|VWYDf-Vc+H)s7gp_ zJ630Do0LmE6XWS@=usksmUag2b^lgyG}D@9h#^LACRaNkx$9ZaMoMH* z0$aJrLPQca_8JnGWI#?jKz=##9!UhY7c8Py6~K|;b4_pq-oa8Yr6beSJX3xvhA2pT zhntSod*7aAsTbMWh-VCzLJNPuC~1!6E*|`l7Kek#eu|IPxL|R-sV}}X^jN&EC+sbq zi~j!Whq6tE4Nlkm`;NEWs&p1*?g!IBDYeSQku$_E0#YP-^W|~R zYDAeW+~ zWX>2mvgto;vO28iQ7AYlLT34|s~H!nPPw$a<_f*P`! zL52$OIaiJA(DE$jWMQPJmsrbE`kqH)a_B%5#})!pn#%Huv)kK5m`J)8kYKlSFiLD| z3V6)>jy<^JN_0FiX_3^RyA@EFzAg4sc$d}D5n+wjn+Ct+?O#VNm~!lMOx$n123b|S zElFkePW}zMc?#5qV2*`veg1lNGHzLimU;7K8nZ;9LTC240zZz^`L!4Oh3T?aDM4u$ zVTLE^sl)9Fwc(S#VDW{0q1pMw9FqAbFI>lbF?!RT9wC9$@sO~2;5 z;&ZNwh(}-PsS|7fQ`UFeqR zpF4_4G3@$E()VnWO2Vs#(V-$A=A1O#6>qd|Hee#eS#ouF73m>iOViiK@V;YxFizHy z;=h~Q=>8-{kikFU+m;@wYLzY>;bTBspzM2T$@3P62_zeQd?1pn#&Qlz9Q&=Lxrv|bO}tOF;sd(Om?z1)$`zFa{CMvi6+oLm4+#v4~li8 zI397KWrl1Dd&b7~^d>E?2ybr|oZ`oUnTOWSwTauc=Ee7$hFp93&@a zr*?qmB#0BBVrZPWq^jFj?Sjg>5{oNJc!mLXyWh0be5j`x_-TG7VHHHf08Lm z4FX;Lc>zTKTFu*N`uZOb>W;_ddv;)vdd4-y4>@NKqsIJSJ6;0pAXCqJ(hq!eI4}C} z=q+TLz%RD5!Z?-%Mgz%`KFapB`&Kek>B*2p0^dkFlL#-gfMkd)GNC!*$6tN~^0pZQ zMd?g6l1B_XhS_5@wHGXN74IPI)_}veH1TaF|H46E`W@stc&2#^@6^PZ^@CQ6GI38N0EAh0gv%%6-Y@vuYcz_^j zfR~}c*!Y6&pbL#@b1#Erzi0yd1)QHmi3mu424zI=7GzM8E+qt-(tYbCFPG1V_hJ;8 zP7BOqY0{l~wDhoZ%YjkM}; zwru!KZ97e`&Zwv@FVmvCF7twnJI!6-tdA1?wJbmj%0Zos{F#f;!AqiMiW*c*zpr9v zL?&$i=7~*EFuL;SzGJ=gzS)L6lE3c z^t-0rjNnLHbGH79DZ`9~W5sNPlEk9Rg_PMDMbirU+f;EN~ z0>8@DG0s8x8;E!VO%N8pMY5UfM$q^O*$s6@=8kFXfy9Taw9BO4OeMGl6Y+Feed((& z&j`rch<)NY{wFEQUL$5)JKsT^)KqE2vBe{O(|%e9Ha-e7aOJ-MDu~hUGdE#z(brUO zZHv8;FdeOxF+k*ro+bl4jV}xw?Lb2r_{@D5jznX&WTD&zVS|lpdJc8 zJYUT6s*qMq+UJ=oYnfx#?# zgG{iwy@5ptO=On3a5|bmE<`jTuVr?6tBM2Wcrh;|==+BClKrjAFE!iM>p2LO6Z9xg z{8Z}SZt=IERoZ=|I+Y9Gq@_jy_{*JMh=$?2VR$fjKl-JaD_&CE)wG!mjLM<^`Vw)4 zc#;Ha5-qn}pF@02-#yg){B=?0gZW}je=b!SC@xAIKS*(CQ zuaTw`<#yO;HBhUfnZyzK#yvD;@b`1v1f`t z(01`xTY@_;JO`_-l*|4})c#Z+j5T||=fYqKBU+F4uQ>56OnyAtDM1B{({262x{Ss5 zz6}b?9!pcP#Jdiu{m-E4_N>(f?(_JCBzXEHRJ#hQr&G7!C(U~UYqXc3H?1z%X_G7x zeR@f0%q^YE0JZ{Opy)Y4F{S6h}>0+{Mb_hk%HSlQ>~0S)iY%AR6I+C9bM$6> z%%*y$#iY3}i}qq$i>P~|K~2IlYoWUBJ!LkU*yfHmCdWb~ht(hiq_?w!OeTB{IgU8ln>1&&h#x7<&@ zSsL}1JzKw;AIC8(`n>`Ps!SnEcv5zDJ8uqnwbDpVPV@0eA1UXMpPxu45Vo3@4imIIo?URi}TSlInNI#54Y^L4#c#1wl3{s z&@6CUHh)sO4EbwyD~8&~(u zBrA=;!?(}c_O;j~P?`JHz8ehFzXAea-!m3I4S;Tdp3-r(eHAvc&Fy1;#lo6H)i>#G z6hYw{UqDdgw9y;JykgmnOk36&0LnvUKC{D`xp)MXc4|kY{VN&areIVlbfe%5jmRBY zsHAB-&o{jMJm5OXhMH>ZI48*FLC9K>jg|n8ar4Q9=G!I()a)3Toz!05^o;^kKK=U| zbv@7G4)qB_jhuAbpq??D4&1r3R%R5J+trwe(1P@Q^p}vlUv+S+rmXJOf+ob2es%p* z!vQ8I!1O~JFFV-(+}&jKmW+5W({H(Z7-Mns!RC96dc~A)z=5|rUBJFY0A~5xbgA92 zLdor3AT%Pui2#xOw(sD}EYp$|2@2 zb5_Cl2Jg>bq4TBjDnaBlB*m9c0!m>dlnVZ2`iENCMtSh~OS*8O8Ty|?`rj-Fpdq@M z7u0hwsV`_NQEp46gZ^C0~NV{_25+{qV>ZQQ=B_Ysl9 z22{}g=t;|fU_CSmN&AfG(y*YoAnbgHZ=Hf-Lsql@GD=yp*r9kxdH$NRV-eQ+jr&-~ z8)$oW5pGn2W>pHSQu7#2wbcg&Ff*`gQGnp6mP}Tra_enZCYvW69hIoA0v^jt{pB zPb!wIn6nV|9b$G7R5CI2=%66C(9*Uc7UDW9g=C&Ze*MS78B1C25e7jbqO(0TgGDRL zAH`7;W~`jcZ5W}1d9z!%G&$rJAcu9=s_`K=8T``()^o6d%(N(|C5?CvLGrBp0|5Pa zWhEH~5v6a#$mIftbCplsFLe1nIaPsYT}1>BUBJQcZPdtLplTW}78i7il}Xlna>S04 zA>}L{^k4kx#g2P(FBVbIjPCA0l62>Vd*;=nUUMmkB|jQs zHkSti8tvv{0bmyKEGAB$MsJ*!EMft`TrQexFpu21j_R|(t2}8}DS7eu1BlD2G{hEy z2prS4Z5v9>xLh15jM5_opT!bwgz@1SLXimHhs09f$Pz9gu=u#EtdE&S-yl-cnf2t` z^0Y0h8o8gDmDplRk}clg+eWgY+7WJkK(YO2pdIqg0Gxjd#c`*NqtUeG6n2r(o!@*4N%g%evma- z-*zCNZoABH!Y-11N^K?OeFZqcdp72XL7&Hkmw?5sf6x9Fl_kG&Ie6IpyANOzMEc<_ z80w28;#M`()#{n;X>573)Q+Q9z8nIO4%6ll`~6MpGJE2L_~(G&gj}wX=nsDFHg^Zk zr_h(xXBXx<-WiU}rra_I3mG1U;-j+G_3&oP8CK1mGmviO!$GWq*=rUBBNq9XRPb`D)lF&oe1QCtD})SxXyk0t5^yVef)_O zJW_s0%MK!ExzREmL0Ntt3Rz-Nt8<*W8ZmM@TWzaBy)JWT_IHR*`P=F}V|nJ|sXQZ| z#OmPASk*K;v&_Y`GEapSH-X9PgLSV(G)R#GsZbnIKhZy?qzc0@=6{;xoH4WdR$B(w zXIwwLtTYx*34SpJDqsA@1#XEB$>9o2AE1#ce5Uh^qItr`67B%!`2)rD>8)g|;>u&- znmqN_nw3QIN7$)5e!e?gBQKWW8=_HRLHh$Q%#&a#_BmvIT8IlzrNpSRuitq(5$_XA z{+DF$2bKtV4wh&99L-#Da|)Z4aB?h0Fp{Fa3PMJ|uli36_Uq2c7%$-<&ufC{JkuXJ z*q_@<4*$s}yWPUc#SvC^KYF)Z^fPyeK#J+!vMr$gYW3yp%;@i^;Z{+%s+ zTIzzWC2wIIj|C_EqmRfv+a@y4cEBJwVP6|F%wD8Io!u97hlg@;f=(cx0bU|k^&~Vs zbRGQxWfRQpSh5B?5*Hf}hp(zByoZRWo?Ncwd=XX#zvF?D#11a`%ZsWEWaL;-tIb}< z#4m-U=r$2B%MpAGKjEEPxhfB++Z+8-Hss^XN3*N9v+4~!Kw{vb^*DUiU{X>Wcf^Sj5wjylTCl_H z&M&h=7GC0{+nHJNF`o<9-tcqUOoaQY2S)_@vaO^P2yh3_6kxQj`c33({rkU>y)&+W z=!&9!RLn1_eFE~vVk;w4Q!5%9X$Hn)wdvrwDUU|@p;6Lr2(KP3>s>ve0{g%KoH6Dt zSLKWGzd!~y5P$6TpV+0p;wZ(#wFMn4E)|q!+>q&b@sTsfWOB}4G){gs_>>qB_QEP?Ny*pr|>m0=cC2 z#cu^Ar@3=H??OMp7#;4RB#CnyE7k{(BVIERgWTWqeK%T`0k#sLd+6&LI{pkC@AP3)l zLaYEmK)$~fS}lrbf&l?KOP>Xfn12dH7b*Dz`#zJs%GETXeM#hMwuD|ms;==vUUm2& zal>Cj^GkY1e+R5@&;*@SW8L66+x+wX&*U!xIO(Y#d8$`DY`UN?^?aR~z8~NMN}c4{ zOtEbtN>rWQu-(PG6PHt(hDua-+NR#R4!bIRd;r4hHo$qd_M635+2oQh& z_0M%gl>2&7J{%VdZG)$Sc(f<}ukKuVnMDBPF_6lPP$(v3+sSXvI3pPn_QuoqN2~j& zFnJ|n2x^(>^NL}`dW&w}iX%l)2EDlsuKjwW=N3>Wd)RJw7n70f)*Pu_phM4xu9>pjOY4@22{v7Q!?a3C{AFZzTzc+yxFgAQv=cv#2;5oPP~_^&FW&L21}K} z%&*F=JM`_c?=db2J+<$pg$ju`tvK5+4X$#4nw^vd-x;E_;ctfE>AbeRRA=9BywRZQ zmq?7M#{_WKGjbnA+aY}oZD)nJEQ%Bzm!FNNpYyP@Izk=K!D%bph)WB9f&RgmYR73X zP@aVZmo*yVi?M86`BTr=M@$Wz8Y20q-QX^aTEB{M5TV(C!E+vbfxtR?QRcr$4lj}B zu3VLtCWC@zRG=yk{vS3m@n1w4u_9nQ<8l6#D`NawQQ^UlIk)WdAIe};I9FUaLt%QK zn!!W#(v59Hioe20SQY6w50V^)%kH1$(z&TCZgu8y7R!gE_)`sB;2_DX@`T>u2GDpFP*enHq>^zJu;%h>b-iiGHUr@TtwadX# zW;MoX)lZhRCFy{#cONm2s`s68O1q|?9!~Rl+zk1^D$AT5mqIgX@*|Czk@);fmEdlC zVtlUGfkd%tq{RrucW*XHq*NBgjM)vMEUJ?u z{uW@$e?a7cuMdwo8&1n5{}x3MO~sR0WXlj2^@9a@-KngD-KTA0i@?8B&swj?-nO*z zasaa;^yEKj_K@h!+25X*%zm*h;bk{g37{LXkcJjh^%hmymO<{b!9(8D|1y{owl``a zJX?v+Z(!}qkI!MOwZ}MdErGBkW!~wutow^}qG%bAlmRuFz&@L&9{Q6*r;^HnTYV)@9;@spJp6qQ7zKcmix!q>APOea}AG788T`?iL}OGyNl<$dk43NlNNePnA(u}PUUngvVoenAb#o~T;C+!5} zol}Z_2FUM}%e*3dmBx#KhfGhOt9F?z96!SZX0VENh{H$tNA;(FW7)q>58r*@Ab z*>-X=qBwl+1>M|6E|U0y%MyS*2Z8hd>j=$+L0X4wrwlu4(7Q!=m%71;_FI~31Tq@W zx)!B8S3diRB`A7cze1iwg98B}d`*dL-%?&UDEB+r+XSsmj?!VPFA-vBIQ8kI*CM1n zag&MYeUKlT2uNk$Xym+*mcD}kWUi}^w`Tw4VPD)WL(e-G93N&(zjD#P$*%QUcj2af zz-s{h9LEbUud$yEIB*rRu@@tj-Iib`zn}+Q7}lyfrG;TMl^^&69EUzi7QbSZ5RwUU zKJW*Im%6g&(@tQEvq*ma0S_`TVu*9fv+MSDSKE$;-A3ZZ=B^i_3lZX7Ge%Yyq=Nr2 z4HCgcpALs4U|(Z$fq=R=#Idm&;T%F#BqZ=o+ho_uK4Ny6t?bWsN9c*s)L+zX6Tf0+ zjk5==6KC&8$c4D3l{xDUu%$G;wMlbq0hSzChC$w>lixpg5q3(FP2*4S*0IFai@q3H zrp2eJ-;*=t7b^XqxX|raEGVPy`{gkRclw@|*&7)^&zIDq!Zz$jC#F;!&s&FBfv0D0 z=YH^;%&FMbQCNcm{2Ix>GGfscRViz=0(3swS~OdYTTOTwFN-vI3#*_1X0s)t&x{K?oHt*G7?9UV|yOhdKrT^#UJSWJPdD^>1Kb(ax=Y3rVW$AB)8w=@F-Gyh# zN;bb>a!kjU*1}maHU2afEtt{qBYqRTR?_YFTZs$p6qFV>{1>H#j(uVOYFy8?@d%;g z)RZ1sZEJN?cD2$)ocbtafxbLt9e>u7DLu;DACqE;aRW$e_8a@!xR+~Va#J%Uc41VM z5Egs@O?WZ;Iv3}zyrhUu+{_B^W?Z49u$g&jl4N;^vGq-%5og+)}phv$Kj1Wx^+sqCGUZFh$twcm5>-uzj`jCCx zgzjDvnCg!8pU&Y^Eua?nfSARpYnXHEW2J0r+UC7-uPjX0v{Hw0mM($;fqG!PTo_J= zzpA%T$7Grst{&ObFukT-Q$9|tKR)cgK~AZl5yN`0qC%!#BKL_df>C8u074bZxO?~C zW=FTkwVJRl8AqKsyAID=EP{?#7?dV%pHR_z(E;|GIzvB9h_X7J*eB~yht*GqJkpbg z(DBbY8^clqAq}sPaCgs`ZwC0W#bGE`H02m%tnX~M4mT-Q2H|ESLb7S#^rDe6Z^o@JF+N~rgG?A0K;N|+M8>MUO)a`%eo^%l{wB0HtG0TdXUMYu z{zM!13?H$~k+=&MIow!Ke*Rk1EpM+0*&)g*DaS89Oiijqunc11@Q>K0K-KPe+dfxZ z&TWcd4j)qIwJeel0^Luw9y*)nvvp(_DPsP^bJld~|+sQ*xk(^wMUh638ON!Z~ z#4X^I7Sc1y|47q@l53dvjfwF|oMAP*Vw3r}?~2b1T+IgSYfWpdGE_})ic!@VpVOQu zu?pALfnnI86&K?KD*+0Ff!ljnYTp4g7kc-go;hzlE_C71PYm+`R{MwbwY=_@a;eh3 zuTtUHwl+7I{Ztl`|BLFU)2Xi?&{IW-T3!hyGskT%Ux!yLSOe5>%KH)D#o5^+2eFRS?v=9dmxep| zwzPX((XynqkxU*&qNnSL*k3hom$RqPjPK4NcaEsC9Nps`>|Shd_o&UCQe^9iGAt5K zo1awEPu;Z@YrkzhxjuKJNjj5>^cM1#%HG){_!}aUTld9uj9xG=gP72Afcj0I zoNzuFoLDr_l>l69q|ilru|Q0;1MK!%Sh-;QnLsa~<|bM+H$6Qb*-}3Q(rGVApA(6@ zFZE_L5qUrS5-40`DDK*S6^hF8(ZXer+e5Tan-`Uz+Xi9Kv4*Od^=g= z`Gva;_G4l0aPlQL`gyB#btg^4ynBDDX;0p%QwO&yfR$sPn@bxQf@;z>CT(bsdVf=6 z@IAeHJawMbXh;sKjD;W&5n;JE8Xr%0mASx`**Y45w$PDdbn%XumUM)Vx5msbSUvfk z8JtG{q4}nY^vk$6f(ge;Ow5`9w8pLC=IP5)82s3)8y6Lb=%G5`I!}&cSc1BeUzA+P zGJPKSq?JKA?-!zh zgLs(Lw(97rl6gqCWRj?&d`kd=VD<8vJd7oo!+p(Urjz`D$HnjdwB?Le(ZfW}Csvot zJ2})c_gFJ9;z7}i0%;(SI2jH<-LKn0PLvj#lRc{02X|&e#<`}gV3b>fr|AeMefAk` z9W(3rnW;4rB-3$W<0~K)582TJW&#Mm7i7_u!a-mDt&QiafSYeg2?dUy)Q?cKreuAJ z>B`BK?ZJRUn0l?69qjbN|INyAOJ{t{`zCeYOv?A|ItVL(k4t3sf) zINrNHD>a!s(s=kh{2iJ=NM&s!Uyf#ZZB?+qwd`<|Xpc0n0Hc=ufY}a4PC1Z&6ID9u zH2hr^DPIl7OQZm{{LQsd9c$4Wi0{k2Wo(-SonPcxjz@$z!^a?wzk<)W@Wr@&^CXC{ zXz0Bd!`t7T$tt}VyM>>C?N?(BW9`3(Is-OheyjBd#I~tJcVyD5UIVg|oXAh{Z;(0# zON{mfNxLF?HwB!Qz*SRvTt}C~=6He9li%6v;wikRSKR?Pgu%)yfkmmgktiKU;difE zCtjX04Ri3Ck&C-LaVh$u(3s3{oVt{F_1y4}UVm>Vg;o%`xm7e1zkZi>a%Xh}TSbn$ zQgqtOqwJE@vfXQ#0YrV|4iCx>ZCkDO|xMceiQD;RkFFohhAL+q+4%%CJP~G zf0V|A(qkDzSrd)y$Sk2|Bg+~e@Ox-Dfq5G(TjI+ev(0lzQ7mb#^#U$bD}2%6$VtUO z37gdoS9o!~3$fDo`)2T_nVj9RxZV;qhB34PHTNx&(BmPy$MC;y!8ekON@Gk4x`Rto zmNOAP4;g+C@u{jy{~mo+f*ZEOfb=HdshU|QEyjzYkd9tmGNs*Um~S@rnrX@)8;C!r z7K$RIWsIn&wvBc0d>HmO+iSmZ0ejrPCEtc8SqU&z#iukC(}6ZI-LTVJ)z~{$Hfj*} z7V)-&J9>#0)2V}&T#MJ)MP7&9!AtBSBrZm_$eU8By;=c>O)#{{!Ru^|Hn<(x;0 zKxL-7lY*VmKH9T9K;S+Gg4(zE|ERczB~bt*Hnwfswr$(C-q^Nn+qP}nwr%Y0aX+D} za!5Lmz_2`7-e3Z4)sQuXvt1g@(UsUHy=p&@=mIFtF2J`1AVo39@0IVRwM+1;b~(J% z!sgyY^&t)|L~oSLgP@8pq+J)!BPWO2e>0j(cw;aoxMZi1;m*7P?Qn$`;13Fo&pq=? z0b|RJ4HYdF7QRhcMt_qxQzTIW#`5vwdi&VZbksc5F3UU2D1s^celsjxRhJ7yFI((7 z#t=#Rw%My!=7ZW-0JpZBqnA(`b-JGDVC-D4T?Fc>_IslMXm)tb82hMPz(r1ok}TY> zWW{yCAi4*zC+U)VM<0Te2Z%zoGuT6H$bbz2WhPrN+I%q3eoTwqZbE5&MmnomC5wef zZ4&!lE2x7M@5^U?f8OL=E93D}5%w^g`RKP>HAc`=qvYfefu*s-{EVHYl_6)LdsBS= zCO*O|$X;vjwx+h##Ni;HAJxVz3K!COLSWa-jeR3{M}J%~f!&`W8N5|b2Nd!=1>d59 z3ZPf^zc(UBqaJqD4{g{2vckh_UixnQr|SW+(e$}I&stJR8elZ2Av?^Y?`$42=15fA zMfW0Z1tLTNwf{vN^Y5?|y>8%9`kd!IAn5H5CL3s3r}h z`|Tnx>3#)FGlF)D%>>U^lpf~$kW1FVZ8?=EZaG~yFs2v z0Wg3p=b0gR?tUKg;eoNo^-KahGPp+FPEF`U96D`%8-Iv$k#=dX`ic`3%;j1nl4WTJ zSz`FFXFa4){|zjQLcik5V%{24ZT7wNY7;IvZX|TWgAA(*yCHWz z6lStWfbvhvLfo|gwOivsQn^ZuL_+HbZQzKl$9_~bZ7U0D6hJ!>`|AIdXdFJAJ6n|7 zfjdv5D#l2aJ86}Ef5eBH73|R{s%7kuF0;NBKk{QabsC(P4-)HL%jSdXx49OHSHoQ# zp~>9Rm4a_c951e0l{|)pTsXK$7Ti(@ZGGe~QnJzFkStpr9qt9}m7(u8wAOLOph&Q8 zZPcCHIpvl9U!}Y$9t~Bdn)>W}t5W&Hf0Z-E&Ll-~m4*J!>XJ}se2`d(%KulgNZ?c) zB{FQBURZYMHn&0V`<5FN5%H2Rj1U@Fw&3XC_m~?(9i%O7H-Kqrx~%6Lk&w>7`oU9= z(-fI?jTkuwz}K2ev2+n~4Y;7a@X(cU;!noOK&=W4XO{-6bLJ+k>hrbY5;BEcXjoA8 zz&gje_@Y`(H2>|h4EB3f3;l$?BKrc> z9!vF3v_P<}8ZJtXIdp}Oe;yJQ4eC-eX4ZNy_65LogRq}oz#&O*B3=eu&=7lY>><6H zM=Yz>M)shCH@nXJ3E$4JjNirlJA?0kUJ%m&>o*w{R_9Ahf8$Hvrf^e+A&AQt&<}xP zNuo{zrOwi(gRz}u)bALrq_l#AbPu_sqZ(g*!)lZa0(X9)9{%;7ecU!KoHRja0a7Ik z5@3TR-+R?r45D3+F;7^5+YZW}du<_5Qbr2?j&XHMHKith(sGDn{5D2z<@b|mvxHu1 z#l-Ll^q8DH{)XBNW(jKV5}0&3hI8;w2K!1n^qyW-FVjnhGVNu}I0uA4G&>A7}tCmZsZh<>fCF1#*!l3VBs5s8AkP52|yf zN(3k4p2XH4b|M2J$L(ggk&^Aos)Pfsfzzv7PTm=8;auX8GhQQP(Op5yN<150J9+CK)P4oyRjdfCt0F}u!W2gs@fDtfEz`z4Y z!9ty9p3q~E12N?$A1%f(n9~lSVXB9z6ycMx8CGHi&`;q}>RsTBdcib5MQaq>Osz?-_1eNi4ea zlh(5008#&l_Dmxke!76#-E-J>64C4kTOtk{rzaID8;hmDzzFrXFc?C9Wfi7f0I2$s zO8P$NHyZDXuX2?n#tHc$SmCePYfNqSffMF5-L&ym7wp-j@eJEQ?sJ_-I z@g6cT*qd~V744CmtH*|fqqPTELzkExX8~w*VG7p$%3KFXN15~VwuRTf}Au)XIfnz&=0d3Zejil_Sa%f~=yX6uSi564S3n zE0SR36rpv29ADwrqCT+&V9=L3lrzwto6#0`cGSm9c2GtAC{@0_c|NCaqI)+G;k)!M zB;UTILcGPq%PXB?G&MiHL+QN8PtBGN>ja#&&OgN5j|EQ2w-tsk`JsxMN&?FQ^?a@u z0@dL<)xElkb0iaBSb--^NcK}(xM@PH?X3X3Z~yl|-RzTGSzK zSy;v=AD@RCT?f0X6Q@R+FP`_{op98EbuDUgUYV@6dw|XCMuN&S`ejpo!S6}WB}y+t z$z2IXW4Z5djokq|z(cRI?YrIrbsKPon+AP*8oOr&^AlH%Koh-91GrB-AE0QfVW2nm z>xd@9iXSrCciNqQN&>}?l9gIs2v!xLqq2V!MW?CCW|o9r;@SDa@cur?6WrB=Tnl0) z&bID-h?28CxX=R>d$RDk7RLR(9HL})#H&dN172OKNiTHA-?;;%x`2Mz`_3w>i?9`j zv_EcUdAc50t~3=34iw?7GdH-~ifkFoHGu=nX$;2s-w`VF;wG>R=XfWL@9 zPKopBKNSdDz2JFQDu*rPX)c`iamW2Q^70kV`E@#BARxvVz#)2>pENTP=6!~trQ>He zV{o>H7vcE@ql(cpq}5J;CgTLM`NSnmIKr!i<}_hgFtV~OO!RG&%h#R`MvMaR%6Roa zriKV;8*~vsPTHQg_U@pe3n-B~i3RV=v2>l+WEekf%V53gxUW~rz|S{>Bu5l}=xr+$ zT4P`9aObx<+t2-7NWiwoxv7TOg#G2*b2fd6dp`j}$EyK)ImY>))mnx*Aw4QpxkoHz zs@Tf#onS6`eiD7jXf_a9D@n-TtggMx2w{WCK?{GIErYu;`|MawjCc*)D?-ZGfO8ws z(gdapNYFGAMMqVTrm7!PkKnqI9O%pw?);pnEM- zs`B5dp>NX#JK=dL7gyvn{93;{I&hllMhyPnRE5NdA|bvJMHW|M%qygG>>r`HP#%-} zCdr_Ar8%{b3Txm{!G?6eOWUD0gf!v8;NANh48a-J4Y~}s;^V8xxLd&l5a^e#4yy#c zt@Mg*i!qeKIbz;@1w8|f<(F7yl;60k>hf_b4-5%*m=O>H$_#>Y9!LdD@w@eKy4{+w zTUTe9aKJ5Fk%QCs{@4zy9m+wag48a|UNXf5BlP=*IiC_OxJt&pz z2cEjjkaqT7p<>;LkTB zkheyN`-+fWjhydd?PIH$>e^#EqO|;^>_vd|4Wjqy1&F?%1H^P-9URLINw^B2pe`He z24^h`G|COe6mTc2Jh~djKvz%2{A5F^^v{mjAVE4^cv^4=bSM2NT4$BT3|u{W9Eaht zu{(KGZJNN9w?WrDj%Di_)^E{Ps|MRYj+np*9jhfTJuE?LnOuaMp|2%kwyKfYD+SRc zuF4Y8=Ov&q6B{?pc%@?VZjn6tbp-F;_Ata;R?;U8XG+{-2ZME+XYmN>vp|jvkE?ko z(tJ28Z@5Hp!h!2Qdq^dbcYc@<0`ZxbRw#ZuZ$C zzSv8`+CY~7ipYu3VE+eUHCk|VR5d1!8lRrK!a<$*=CRR)#^}$FfsUbCfJDq-9xIoBh3=LBNs= z1@BpWAo&Mq>bpqBq)GJZ^h98cOZo!-lVnc#8ey9%Wg8>`bteL-07(Dyk4q|9RBOLS z{L+0{qKgzfJ0HFgY9(t$utva9_xIVE1uijT-zt=s85QL8b2Mu}zP<=icoKrkWam^# zHv4!~v{vsQQ*_kgDhaTMRwG*0@Ls44mcpj=l8CM;{Gx<6L5ILz;m?gBL|-xv`F>~@ z@t;TP>A-mhjoL4~2U~}4vM=^1@td8)=gSqlT+`Kr*jxZp`xFoDtCkHgWaOZ}IYHVA zMNP9XM{h+xTG!EdWnUGjJW(e6nx zf-aSS)X!?NSJ15$Xgl4kj7tcfhvHFU%DdO^%X?OoBeeZ>)A7HzQWlsX(>mFJ6>K)s z>y&RX5+}vVHV6o0VJARIUTgrVJrwfO{+It{!&-$6>h%QzyR3iY9j-L4I9tsB+|)xa z+HnFl1OeX_Cu-j2XYfD$v$saO!rTw<(&WQ;lW+>hp7s`IxRnx%T1ZXHyY&hH$5`XCCrZ zT0JCGV$WnGysz?W^+XSkm~r=33M^x1v()UKk<^DM$7{g`vj%DcaYMYVyK}r!*;E1+ z!VU>;PMpzH3IEM7-$3smfLY|w|Sb$F7W#nK%_ydV;=1V z0`N&l2)HH^HK$JWngQ4{OVXhLy7*oJ7+Fl~yE3-iT*&@k_Mx(zm~Em&^h~jAE9^jF zu)<`C3M3Dd!1q@^f73CK_6LG@giBFUVFXdzWB!0 zcCKqNsf*ulc*sAFuvtU$zBZw|;yhTX@^ z8d()p8*eWdSg(D9o)PdR?-{R@iuIEddTaL=Rl<}m%T8ORCLD*c{{4=yc#W=IO}o0` zz;`6K0v2CSV;?sqW7!U!ZHqpNVu4M=}X=kHD|Csw>AaB1S$ZT?H()QYS z*`?FLIUQ|NL;e2-iA4FA0d=k%0O?xZdaab!U}(9M#ZI@oQ4*@ZC;ox(Bs$_?ZQtar zKn}T4i$=qmOeS)I2+T`ruZ_NxTQ54UKsWyE`tn{rq#njd1ikNoyeH9GuzCdi3GiF8 zZZ+EE1Q-GptGLY>u>*~Tt$xduA3N@;=#l|bVW%rpZ^T2Aiq$3gL7z8cQ3_lTRmKV;BDkj8M zEwoLc$2=nK1d!W00nOH)-qn|3OcBWb7tj_h<1r=A^84Tdc-(-5J2Mcv?wm5he$-sr ztvLSrw0nF+7+`D7fmVD-E8iGfOm6QJN+K4Pr<*2d#bhRl4Y+JdR;1ig!QqGgj+5V_ z$AsdulgPERtwtIEWtHbg{NLnyEt4hmiZ`>}1?dt28yJCy>x3tc8vwE)h2wAu3sbc_ zI!$r0g(5OM)^$8>C;KS9qsv=)JUca!@NuA2Jqg1kQ%hFi636qI5)}?0Mqh+l?Jj>p zBK?vii_x)PZi_9_TE%&ZEM(i2Lqi|T4mk+q6G(ZJk2wHCq`y`O4&%k46i=%@OrE%9 z1beaIY$KO8c~?C9FBJ&+v&`9zrARJj4vn`2WHkYyOr0x%__*J>pV*-$4GL)TqUfNX zP(3LD&-_1T6$HDp{4Q&)ro6>65-x91!95UeJAxP_U4ShJL}>?UUvUT@!oUdAqazrY zN5oB7UmOr+KR2zXXIGlm5V@%I1=7m6N)%kProT8TsR}{LX0Okm^n&QbH|V6LJNp^; zbxl}>e<_g%zR?adE+NN}0QM(jr$}uNbWUzqS$5Wp@O?*6u|`01%z$Dq=}Di zMQ@%HmcWqkVGvZW;O!~?II>-JZ&wHuP%pXW z$LR3t`|@3+)2K|a0cP1(rU?OgpuSEV_mi&?kgt6@+SCXmCJAq3Vm-iP*{8IX)Y$G?nRp=m!PqAt`nrojQ7fF6{9Qmm z+~lA!-}f~h_JTx%Y|PrzeBjDFh&3Hu*b+d9%g}qvRPoG0a3Sz zIj3h;a;3aYxrfl-Gb1GK*n>0wX`o*?A8fJkte*Y4qd)k#%7*5=-h7gD^2buMMC1dq z%|1|W1C)%lNke~|VdJe7GbJ57p5Sw3NW8<>==aRu$3)WUz_L22pvm?_%&a;W};PJ2{2l?QjxBdBQMQ0T1aA%JWgHY z)P*7^-&j!3FTwGS@dmgFy?@371yYKHuj`k_NJS4Yuk+|m5oP6lEkTc`%(%o``bi#U zJu)(}2>{nem|9S9%{UP;hso3jCmU=ygX^HVs*X+AB9&TudsJkiWbDewio@85SpR=1 zkj)J;GI$MxBgj9F_)cm-MyRJ2zFpHRv`0gV2sdxImC)d(3p`H*!OnLi?!lo0(*mj8 zpeHd4m*AY&lf!nr16f0qEaD^}FS{pQkRs&SZ=kQKWtR}x3lWWT7W!@uNzk5d`YiI+ z;$-+#rlZPMzYCKQ7XWIk9FKT;b~$C0SPDMlOio=Rl<+ZTXWN>SS$jVI%hL3#G*l94 zbXUrRba||rHb3hW%Bo>82_>DXT1kDTg102R&f9Has=L78g+p(j>J4vqb{< zus%PzB$z*!^Y~nYxHk5T0d1VVh2bTc_&{Fd$)49x_2_p_>xZ?~%A^5g@h3*zTpn2p zyr(`EX|+E#?UE-tJ3{oxH*Gv6^liG}3dvk&vVlAopLej=4-Ts#lWUXmxs&1apB9}K z`XvIChs=r%`b`+9X${!GfNOTg) zt;fX*X_#qOR?2apXWG_yC-M;po62AK+K}KJ2}$+{a;Fn= zo|w1I$2s~;7M#yL3vPOH>r|7tt}c5iRQ=K{@1qaK;^_Cz6x&_SNU<#g+>O%OElP46 z556y_lIc$dyIt2Iypxxg804Ur;b@4`wRF8ZLrB4K@LhiUfvp>UJa#DQg^F3wZ!Ac< zIFrlq=eG+hO)CZOcf200Z9lSe>@!Cuz95Z8g;nR^Qx^iuLvTp_PMasQobZ~|RTasD zqn;)GJ0rf;8C%YA7{Y_if{(s!z1k;*GFpd-!05^AbGPh78cPpZo_ zZ6tqrj*iY)c*3pdE!f#&pb8vWJ@2`EfJQkruA1b_gkw1MtKVa${+ zm#GZ+%iu1=@%i6*7P6pozrrHM6Fo2vvo$rJFwZ49`qyt0%sA%#ydYdCDiE)zL+^(Ro6bQc`G?LfxV-|O|6f2OQ&2uvHWi%Ar`FlsfP zXI?7zyTXUy zIQ%+lZW9eOPrJg`)5xAmWGlPW@HWqUsn&Bis#y65Hs)@jA? zd;&(p_Y`X(iWFUl6vroEU4R~g!iN~E3$70LAQCR$hDRo+yNP&hY#z%VCUVZiQ+BS= z$6)*^XgiE$+oeP56=0)K^G`zkFBPjm6o7Clbphg?pHdTVj>=oLw`an*hKmNuQq{0R z?c!!I@?D2B_VV^U=` zvdenp?q$RKYh{2Tf^E1zXc(h|*{%nLH8r2SEC89SUFkEU51Ou@rGN9gb=-H(=|t)` zHq0b0Xk2Pg-TvQ^O+yinR0@+?hPqL-$`5l{Fy_RiEU!Mzk3hG;5oVneUEs^3hSHI) znmeh6Uxi-nb6?|+AnPLb$q*x65VRMCm{a8EOoNnzMx0}m7I)2DkCBe{j?~7$ zx-+}nTas-N88>z2XGl}}2poh|V^{hohkG-sUD|JoyXMJ~UN7Qjf^BH>bir## zy1XeK!;GYxBqme?tL^e4xOKeLPN_|^-RF;N4vD!0xe}HT0FqlzXHPhK)Mh4VhL`GD@BJWIYhjRXpB?L6fT z&J=Zl%4OJZj2F63X5kpd_FY|dTv|0K)$+3)celp{82bq;1S4o!0kF>qJ(zKI&55#d zDujpBVGFH%p}Dr{GXPP^EKId`o4BOV1MK+h8|g{Ihwfcnjq7GqiU1ztQq+~)QsQn- zWJI3b*?m`-94~^IkP!nzCS#D#jL2e;TPI1UA=5p0CrJ5K(jU78mf8Ui%b%iHe4XX? z+hznXmh#1kjRGF{M}spYefjwyI3kRZ(krHIzPRNCkK)(p1I>dQyNgXQ*1U@rUyXJ= zfQ=z>7-uA}uVs(L+6UP1WTyeg`j#ug+F|&QxsVRN2zi{7;%fvu{ROIU&RiC#0&M^H z9tf>Om#V|jF;jWQg{S20D^ot34pLV97=(ZvWYr9Rg^J)0I1`MR$bMU7V%1uYw*a#C zW>E%&+)>(R<1Wn%Z|zV@95?9qPY8v|t!z$b1(38~?R){#Akv8vD~;(cW*?W@QT8%Q zi)%|IQv0A^n{zDV7=^?74l!exP#KP9`jz^JeBBoAzTPZXmPTO8)B%co%&VYmMSW&o z*|Y2I4vU;4tFQNE@CJjfQkM6!-8M#dN~Mk)t20!}aVF?Xr;&{5Ni`7ULd2LybaL0r zI~Q2Z(viz}LnTl3eXyKE$Rzt7spBW&ovOtsVo{Bh+FS3PESG^Cs5H9rPNvfPW3c+c zd0eHUulABTuu>|@sX6JNjx|o)372Kf-*-?Vn^(vY-y}1Ttk9A!6?^NnYexXEaT0W8 z*K(S&7crt80Y>rkTxfM*mmQkik%Qf@%urt`+vJpo7ct}_f{OuGqogVXhY`^thwgjq z<&}(?N}E~^4e86_6WV~j$7GRzH-2Hq&DKMi{vRcqZmVYKQ6@R38n8(4Y7R>p?v2$ogThFU*YrcOzMcsspErPqM6Bz zf!$VWA@?wEhI1={O8@WyLEdRIVDkR?$1Zy@-KbCd+H#CXx?34i&!}HICVzPT&j0rK ziJ&10?b(FlV{~A~`|-oo=;86Tu>^4)&&~?zX4EGP_4sHESFeSidT>$-+f!#I7@M7| zyk^`KDDU!d3}VpZ4wcGs*2u*F(+IS8u( zwaltnQBi5WH_WN86hfN&Cf)))REFXuni0Es#RIo#^b28HleA2HkJ`n&NLABn`o$R= zaP2W(_#^ffbQnHPLyjLushIh{B7*O;5Xdac=D)fo?~l5xRJmf87PpJls}~Gt-Y`zs zaQnumro0x$Rp~=hLPt_~S{x5^_Hi;PJ>yn0;Y_2+rC(y@FKUNhN3+p$@SXr| z=MRyGEQee1Rq@3aB~7w)5Zw6 zUqWNn#n9DM&AK8@{GA>1cd?QLIT(5i= zE+#UTq@^z9EbxbRwgTB(qe_dtuN*(_|3DvSW8qj?~ly+;43u?#78ISmr>)sAQ(;=6YOL1 zjO&W&48rk&*k2~p|9ls?NLWJK+%v&KD|6P{gckBhKAL^sMKd9E3r)b%aET51fKe%C zOP-*-A+(Cnm`;89R&|jZnRw*!Y8HkkX>Is&pQiDHu-lHLlS{}Zyu5#o*w-G$8f_7n z5fA9%O~ZHg8cb(ija|1=lcL2$(5f7Et{L=QNi?7!c$_#b5#;r5Ibu0f7eeZn#`;_XgLI3H;f)<**TrYe7)Ua*WnPAu zXn%3Ebpdh5T~FnUQ2z7ZXz28l!kBP`FSHGVJRf^b79daN8>$jaFxnzHA`AoscZWj#ekv*`fr)s0njy4POT7pAP z)^7$c+*i&f2wM@a3}YSEGH7&P4hoisVoD`pyDX&e-aFdRmx;toj<)u!DT-&3{~Gos z)slxD%T6nPrf51T2MSKUR=1AO9}zY(+CRv!!rR{XgPIeZ-v$AR(PGOAeA5TNKy(7Q zHyihpiB{8ndz;^>?Un7U7SD=kml<>xaJL3Srj@?oBOT`Ojj4nM`VRj3NKGV zX1)DG{>@ZO(U-#Fn~-5IY@tS;tMDppD`t}(cF6yw1aSvnt=g{BUiT4zLK{gL6J&nx?qv_(luTPS| z;ShXr`2DJ4ZY)c;P}+YtOS96qtc={-|?zYBnM^C5heu z<7T+H=JWK(cnRvyI&4wAk4h0{$q4&9(Zeo|5MaB+N3S{qB4UPM8S9AJX%KeGGr7AJ zs376qBW0+q(8LCm(LGQ*)>%SwjEs`nr=+S2Zz~W%^hI5tqT{?mCF0MXBOE?Ga`4UY z;K;~X$DNrv^B3Z(K#8Iko3%Q~k=DwkhBlEh-c2X`a_iWqSLy)&aqU%~HXvO_XYY$E z`L6fFTv1nvCBJ|Voy9}Qc zn1ur=c=m`oa&tr7=;nyNq0)s~@o_8^9|3mB-9t5F{}T`+WcOE=aqePR|o3|4PV6r$jG10pNBY?hV0-rc$9Obd*oS);Uj+^Y=W7}|zD1551>ZRUXzND${ z_YHCEkQ##Y7MZO++qN1lxmq}YBb}Sc3AW{dHIH`&Gzq7Xt+a(+KMKR^7C5^azY=^h zXqsMY7sSv$wM|MAUR9`-;4%Gx8nuuJPaXZhpidLdtCUKHA919eV%wxZG0dg~Dxx^U z8{K*YVH@cNa7WbyNuYNNye^r@1K3Miu zHtrqPeW^}rw;WD1tNebj`bSZy{p}x^90iq((s`NC!+OWqs+Qto6u z2OXQOMV!W`=DUHqDAka3i{b{cw}X1HiRh3@Fy!0Jff~gA?1fcOgXy00mb#6k1nud2 zovD2oa#WhRWEg$~lVV>%9JlNoUjyl8*uP|^pF*t6F1-f~ib=1=9mo3NLW^&A;hmG}k!&Zfs!II2Yw&nK3({MQBhA&akv2v?0MQ$jz zkGiQ*Cr-7S(m40(`=C)$``sy4k>`c*bx~UJ>kP26&HkZ3LOtHm8A|-ew%~PCeY}=8 zrEX7ku+N3p{xX8CAR4%pomiAR(J8ZGZJJGt5%#X+&^$v~By%Ye-2^Mo=97ah{)t3;cr z+>{}1w;wsgP`a3xq}^KW#-J9J(7ZL9#nI~y=XZ2H z;H&fLGm}E8I*;koK9M4&-1V)y>Rp=z;JwrbfNm=eqB?B*x(0Gnd@YxggBQT{KJIme!mfl4Pk|TUsOk2!M*ug9JZbQHjGzTyQQfO(F;?2 zKZL63mR1=)Ai@ntL(G^EIJ9aL#V~bdK{PveXsWf@NOf3!nSGx0>E-2MPQnb@SH>#q z$7!NNj@yWwO3FE*)cY@Xl}*$n0OUiF)O7=3`4ha;tH{3PoqAMTtH(zqvP zzYvN!%HzdO80O)g?_(FS+Y$CWgqA*sjB?Bg5vTBED2=t%s#v(t^8Jt?NddX?MZ}l5 zU`gxPheYf*UBH2lsDqFuOoP4vrI{SNOB#?%;l~gcM9)H&-QbK>fTa}oXWpt6^;LF{ zfSS2QaAn5)wP-2Te?y_cVQ7{704L6Y05>164s6+5F6RTF5wnZL;4dQk)GUH}7l7ct zL2cARuag+axNHEwf*#Fd1D4|>f9jHghUltLl93gUsAQ4KC*f?Dmi;X0V`B$4gnLPM z+}PV_1_iLr4Y8G^5n>7%P+YEmn$5@pwW<5-t zaNI@|BQL~Mh(CVg@TbMySHXZTcceCkJjKBV>!F)B6Vr>z+R5(I9=-~X1!bNB^qNsZ zgBxI(IbZ9L6}mz@cFcUdsiW&CK$zfclA_)jNC0(uXvIW+#s&9GSAggDcy^EJ+9L^vbS_j^p`XQsZSQi!80AD4i+kXTcj{MRR<>;?qA5XpzOB6 znAq3jc0{|45R+nE^v3Zd($bo-cex)b84=m1d_9#b)IfS&mM&b*;wTESY&;n8x9xv} z3B9G=Tdx6vziSLJW|df!4_4+18Uv(i@3?M4YX6T1;^V9Fg(z1VZ0UlS7T#?qR!4ep4k zDU5p1SJ!p!>u;F06#!#row5MaOPB5?$rGbUNxecZ{@JEIF`wVs&Q%i zD6_YrhcW@R+}*(|J+9-mn0~hvyN{k>$g6Z_`&4_Gpm}?!+{2VPE$Jb>rdrTPYbeF# zBKs9|?LOMj(|F#*bWv^HKH<(;(=B;=@B-@@P51;apkk6p1V4L9b!O*eSQSdTDMGGKQJ%^Y3ZNJbq(og15Oz@DJeX$l zCG5nE61mDls>4`>HZLkj%XEN~g3>I7nbn*nQXq(UKuM45IT{b3k_6 zE*YP4DiQL*s{^T_Yad^*rGK9EbMm#wx;l}Z>SfNy)qO0l`S__XfifCW0|$AV>m4}) zVG7^&0%0@&Oi~*K@#gg~8&9?-?wIN{w`QmWevIy1?yInK$Zx%TL`wgs!->I&V7y@DywpVW)P?6eVH1l~2vl z$-IVTg=;=V>fJIFM-u2DPpnv;M?ROJk&#j9AB4GYt@ok=+9WDRaGg%aHc;M|$2LxT zt5!P3(q%m-tA9+r0lpa5Vjb3YlbfVa3mPH|hRJ3y?6uM;`YCz!STj>t5qU&UGb(5r zLNjQKF1Pxq#89;hk80{rr+w-!t$xY%kKT|+`Cd=*ClcQ3hbJP>8u7zMLfNGI)$HEZy(0OfAF-hw=FtQoPOKLR=shoZ6sL*7{WMOJ#=pLa#Cf$7M_ zX7L5evO+H~#Y)A6CG-D?n?&^s2_fcc58MWe(v-}db@Vg7%@61I3$-e_tg|U`^zm`g z9d9AZIETi{W>f@u6Q5W6Rl7H5_?>?vV{JnD?ckziAtkPy=CxjxnJTHO&)awHP#}+> z0@JLvf!`aT;Ri!nqOZG-G{obbolyZjntYp;9#OBE8~GDwCK<$;HP}kUGtE43aFX#v zxz5^af2j*<>z`y95BZ1fH4=BmK}NY?ptFI5^fGZ8dJzELNDj!?)EodFy<^v%j)n6| z^^)^7UB(P_m!aNKttdpmv`a`1QE3)%;*2Lt&TUg(3nX5BIRrPCJlibpRX1@(V^aOv z_!F1dk$(k{^wo@DwzGcx}3&6l!VAO>_0s2vx^Ys!MQNjQ%`*Ou)h}{*!W6 zK<7-D(ZzDxWJ_wcbN%A`qC|9XX;V^=#=6+?!8{_?_#Qk;1y`W+-D3Q2YU+4bh3Gz#uVIe2RQr@SsSeM72MHZ;|U^oMc zy!6YA%^Vdo0NK-0CN#aec;Fn2+f(7Fs~3j3dx5`GA$RlP7gulgFqAc-yNzWG!uo@L zu#X3JNc5IfG=WY7wnl!g_~rssZ&6adh~_!q3LOo}a`~rQu2+u}6eBxZ$IEFgZ3p=X}MJ^X2bf?_&Ow03O-Dn2a#Pu^?q0F40+rn4A zn>o{tb|rQ!*}Jb^T;A4%2NoZYlaMe|wu*+o9F5;(E$ToSQIO*_9R|Zt00`yW^$QR7 ztPOHA){ZovZ;`WCK5R7m%SCCkM0`f_U`X{UnhA!TV>qHQ-$Q+3m42WFv;IVw`J($c zwu$}H#s10(SGb}K0|r?CP9io_^5tF>5D|6kU=n7{??c)a3(UYQQiFtW=yWvjvYH}) zk4_<$f3F8p$A2&_^qew%@Xq-}Z1~hRg|uqD)~U`c?>u`TvRY_%9>O!1QP(NR(#Q6? zA4oH5CXYJkOtF(WFq(+WEeB3gTj(RZJJ>HdSp$E})t*&Jb3121O{tEs=b3HAqIs+3 z*z1TJ)jh7p3NNp30x9hSqS3&?ZhG0I$d=HYwRDifxk7LlWv@e#($BC~agonsQe_wv|Uxyt?sQ=dL!v2N?96G!Il|2ya6L+ONfLvlhB%x5w_iSG3uy8HOzx}#i zxMPWHPV9?3f~O?tp__(YFMy8Kp0F>jJS~?r60I>L!^OQ2z4Pzt*ruNfnbPnzRW168 z3^G(8DZr@`31_n_nR_i~!%#yTNaxKxuvg|QIl3`2F;2X(N&&{cwkMl9ZY&as08Iq- zm>7un__D&N`Ns2hPx{#$vwk8$!3ks)hzw3M3w-mkm@lC2nxZdmM4=Z_fHme|#PGz4 z;IyvWZcvkN13GVN&@THq9>2TIc$e}QDb4Z4QYvVT;5n_!iMokdx9g(2DQ8BuyMNhe zOV>{T>=4>H^#QGC8A6pdrDdgm0ZIJi>!x^3CosWW`%0b|m~);``@~M>&hFmc8n38iV&=z$AY4Xt;I$zKdrD=eyqV~|Q9AY=AKHw8D}@^3 z=NZ*R6!`m>sTlY!@@m)r2>G7$((51-r@3i*TR+) zn=lYTZ)%Lxlwt_W7s|f#@egX4>C-0}W7rNT;5x?O0F>}5z(Jd3z&&0znzD&YH*hFi>Fm5q{ z!nB9olIa^y;N{0jG2FwWdEQR8&YCB~)-Heg+|GWG0!GM+IVhrhkkI6j5@QARY(JpZ;y$P*uR2*#l(0W@lDGkw@O{bdS z7ZSG5&)L%aw*q}njJ;Mm^MbP?GwaftjHPr)(QM35|JaOqZ=eBf-GUu@4y8Jbfaf&V zonHgu|L*)C_3q^yT`;u1NsOzl#+0j$Z#KK?RO-ASv$K&JC{t^@wF1ce;)W=58QXE1 zbTsEX=p&#;sdZ&U{ys>3-&%^k|8`Ucm2Txr?N5LEDm=#kX2^+gkYPq4Tl>?HUvg0?@?}N5vR~Ar%(h4}9a@ZD&#|=y z$(N=obp&ThW@Fng+jEgR&>f=yh)dTEixUrLD1f$5%i1EUWOZu8!&8|0jg%5s?2B5 ztS>cpu{);&1r;5uq{ehV9@;~NM7!jAS^fF7JTEi0%4T|&Q4jroQ#T{O58$Ka@1p&-z;yRLFu1;uA(?`SUULfp2^G^(ZH#!ne*EQpW@(eT8c2?)qpOW3* z`ogid_k@}jYhQhpsU>!!hLdMKz$rf=tl+x{cy7uj9YFkQ6Z6gMU^Vw_4hhQIk2miq zgrW+6*!Z)J%`@f}+2ndQ(W<3g@w}lmd%|*0mUCP-^S~-2r*!)u_N>;>v|;jCxGWzT z*7LU;9O&c^x?8*FR*^%YA@I{eWerUNpylO|!||~UFP6?!Dsl8ho;h^jPm~=WP*HBV znzhyj%a(%?0An7@QvVTGR7d@s4`;7yPzfIY)e;%M>{^|kVJ3vT%=FDg0ZE9*0I=*3 zvCD$MtDJuK`jiF~nsm18>(tzPElcZr~GC)#5l_u;**=| z0?0EH9nl-awT$2RVh`*cF4lqJc^w~7`~iWe&WWjJD;*F~p`dmV)$ge*I-+F5u!q^_Wg?5IYEMj2l?i zZvu@h-8DMU*UtsVvNv=@x_9foWyM)GG7g;8?OR~;ebM00`4!Q<;#+xk%j=I(hZD{9 zPKcO-Hgh;a13^=3<5Zg!LIA%%FiNe%R0)c3rYS84A`F!lpr^hJgGs7Mx~IaM+-6;O zr{w#9EZTe+_j9^(PT@9gIntEQSD2nc;3y& z!x(g>yMZTDx*sS_#8>g6$haVCbynoqLHXig1C%_u=a=oEzM@E(k?=#9i!_X z>os0K&2c!IR@O1^{d>yLbU5;dPy=fV<%1uBPzuN_lzUO-b@BhL;CGXLt#`{H!mwWD z3;!8NW@z{|EtV>^Y+6?D%XIgOV6i{2ksHl%7fnA$&l0;MQduo|K(A3_e$Eo&Rp|Sx zx2G_z2SA2^7w&bl-=En@ZxQU|twAalzc<|) zh-V8bQyzKunKQRmi}l7mC=*MS?$6ecI3*|i5rYE6*4;?xNy6Kr`fgfRN%$dF^jWiY zf3l&U1EG$paab6jO@>lE+4-B6)*7qF;HBIlxEM~6Mhyfb;=nZ-?ra_anOnQ-BE<>l zc2jVY<|O~;Pq1rpk~dfx*J(ipXp9K!P{CT56V#(fvHV&i{yQ}=dvssqP#XwL<;#P2 z5N7gxXr&GBp{-xj!A%^7ex@|Y-5E>`@(`KnNUswuHLR~60LT0_(%$ZM>~x#K5V z@kqa5#R*K_{MIMC?au%sq<`WRPCBxzs9S7i*5XAagV|m|@q?_o?VCvXmxT6wJu%Q{ zocI4M0)-RFP|9~GuTp|c@(we1W#1ath!8?VKuGiszsskPG$tlwbc%$hu2l<91*R(( z(rk-OsE8svUOV~iMi!yhvVW=b*@cl~xA6>h+bIO*#&wLzx*fE;iI^Jr`#gq4s#iyC z83l{*LtqVmXEUgts&(6NpC4;#ea~$E+{YhYJA7kc&^p;ksa@c~v!^!=jd3yw2|C`9 zH5VB_@qE}#H2AJ{t*xwVe|*mAyYLRWBde8!qbx@R_D2@K%#2IT-+<*yilIbOR zbM>`sI+{l>+8R#l7kS8AhRE(<&w1)C4P>(KK!QuVmejj4#4LH*wixe~<8W-9cS+Vk zJ)#p0eMse=%iU8=6SwI7#q(L6!mXZ^jwx*Lk7lLzPFix3JKCV;4Dzp6)OW>m1@Kyd zAE`GOCzf67eH%*m21|$7%0Cz8C<#wKewcAStYQ>#PLA*0ykwrJh7F3+`4LE@B#Xvg z;fy{`+ucNQm=c4yU(w_QCGN(F#9HGkJRNM~p}cd$;pDN{_T>=pq3nL_1iVvw8eRG=+dbd99L@a7AdIlwWu_e9Ap1WcNjXZVZd|k)Z9_ zlPl{TedHsiGvylacww6jebt65v`b0vDtU_`i=i*Qh#E0Ay-^-COQoer8%?}X%jtUN z+Z|gRR$OrRPHg=mOELAgXW+j6H#YJLWo~41baG{3Z3<;>WN%_>3NkY|ATS_rVrmLJ zJPI#NWo~D5XfYr$HZV8}FHB`_XLM*XAUQcSISMaKWo~D5Xfq%%3NK7$ZfA68ATcmE zH!>g~ARr(LFGgu>bY*fNFGg%(bY(`I47b71U9i5aD2Z3xH8uLx+ilh4r77KOq1!&;Rti z>~scOK>+kGFK!?^sDnKS;_?FVzuu?`0s&mCK>$mz9S9($rmm-;DhFVcQ`H8@fgm6! zpdCQn)yxiT4p0W0gCNczCV(Z>31Ih+1;88%u>k)m&6)iLOTrld1UNf@%)u|)Kpy5G zhd(Y>fCI?M9_;M=@(uty1FW2Y5SN!8xIh75h`F7s#UBDMc1!4ALJm&Qmk9e8-wT*J z)Y-+^+zIU90(i-)E-m{Ho-Wovmp{3k!7pwA)bb_L0&4F1r=-8W7nm2X3lI!(2DpGc zT>j)T0|6|+&JK1!&zIaUU=B{;zcgH(!4Ru|GGGNbfvkW|7Iq+K=NFh4>_6T4XPtom zRz1+c!OrvVZs^~r|Kb34aR%90vZHZvzvMJ`dC6@BhM;l$859MGB^1EL`478=tHXc# z+(1r$8^ri$W|&?`04<;pJ5PWG$P$f173%Vm62SPsQn2(`9DPeYoHG39}NE| z5Cs?DOB*C0RxeU;vUC2!2zHhQdw?v|!7k?30860VOOO8AwILQDCp$0%^rGe8HUikV z`1tMyFhXn8uk5c`ih zRG=3BwfKV}DGBuec(ZW{@Br9&I0aq~8z&Dyh*!w>f5`V2i0hwg6`+d~*aKkjQZy&m z-_rl%`RB^$UtnY)=1_}2bE4@2gjl?c)_)oPxXoRioL&_Dd)i-0{?GN_AqRmxK;~!* zb5L`UV4LKxDJ~U+8GR$t23;Lo@O>c;`PEvv+5TnF_7BP7>q5n*59J~3HDkh0jh?fI z4v*W4%sT@e1a=JVNuc8_GG7{|&TO0&`ep%^oj?xbe9V-eI#J#yGslcQ!`e zX&8ThKq4P&=5azfxn}aGam)69O?&0=%Fp~uRSbR$b`1<}Mb6<*`uP3pD~6uI{=VM! zGsMnqQkL`>R5}qLpqLBN(_ox7$aaq(C#?|p$8M@ygp zRqAzuSdA6XKD368ln+U+NA%ORWQ2vy>NvcWPtYePwC5>ay7UA;n;z~I9-93JbTT(o zD{b8}IdtNOCU3*6lNW$5$W zia0J2Pu1ZBvoRr60*Zzpbw>P}>YSS!PTEBOLf-LsF4122%_a%&7TgT!kK^-PIAP0B zj+2NX^U#8A?>u8@#F8Gy0QOUrw>3>9K*M!UN5~p1`a~INT3aqQ2wFyLE-`4eXTMff zdyPPoQTcW;SSPQ_On%5Pclsu&qEG}}h+u%WOjq;f8s-2!5Q~x1r|;^SBRA6~(*9>u zlXAr*|01%USaIh-6OO_HnIwlSiDdeic>N#(AvdhmLdIP}>}_#3HYJT-+g0B=ey^@! z7|#$=;jrf$V4$3Y+YfnET>`ApR?k5Sc+!(y2VI0%8dMhZrJQA9${Cl6K^|E@G~S+Xk|CZHQAP+h)b#DVv`& zFas%8Dx;IbhW(<2*V`6(MPLzILU#T7qg@Gp!rli=8sKx8Ku2xtE*&{_|ce z+d%$!2jKC_w`f^TN=#&x$O2TKDEi(lswO;>$oj^W=<=|&%a=97lZD%QvKFVd+(;hI zE95(!9y_~CL#Is~HBgeKo2B(}DN8QfP^qe&YV3pkTH2%G^4WO{|5{XcA6ICi`*Ef< zEtKCcJm`2vuSK$ltJ?ENUnIg@zrClttHRZ9vTKvXA5ZVq$I5*VCyNwP%8=*!*Hd8X z&|e{}wtZ>nBu`vpk?QJfj9Zs+5&2F~lR!h<^-+Vl>4xwU=|tw5o{I5SRKuq#>cHn) zd5O%yuyC4*O44X18rpT;m+u6pUWF!Iy2y>)ogS9d!0`K&a?B68i+BXxy$oNhUbEqG zm>tNM!B&NqG3k?Fw7`9qIZ(~Qx|i|}Lsfco*GUs8Xk8C@V?jC*a5zw+8wV)xr>e(n zNbhOvlhkFa^0On+j$|PDN`&BMewxl1ZYtTEk#TNG18#UfJdZei&#AR`0S&s+k*I|A zOB$hkRJ~t+q&*z}u(g^ns@?^@wfcPoS5KlguJM~4)-SzwGoevC@qM97LvZ`JHA0=8 zfN8VvB<*vMkFPR{%6YujtOtr6LxIl$aD8QFY`Jya^R#zJmX!_Kx>+aXe2$q|iN}Yq zK4!3zKdkpvMHU2X0BjF)6a3V?%2AdR?o^gaxSu!?=Rk6=g2(-Zu=2K4^o%FODp^vT z_rZX3r-6uj#8kusL6OW|F1()TGmO>dWe@?*RNnjR9^l=6_c;dx%YM=X8lOJJjB{Wj zt{ZVEAE9LHy4Cj-y@w|*Uu*H=!CwT`QYb7xktOZ5kD`V{=|nwH%6^nvYugAu)VJCkOz7n?otU8kC-hzuTMt+pqJAhpvIHLrR4B$*~=wU}kbd5dUh*fK|?<>Vq; zgLN$TR}T2oAg22MWxkyXq!^gLFmt711xv^8b?WGS%t)#6#Ju9V+5M2^kNo}Y@L=iM z#OiFXohhK1&C*o(jXa`fO_n#7T8NLFDET-7M*)uvwuwS5+*WJ|20KGp31e{m{@Nbp z2tIv!k=`fQ_ekXChB5{Yo@CY-!2Krs+gUl)8sS2$oV`h zBZ!&s2Wy*d_vv$OxgNkDr9c0oWm{OAIb_SzBw%K%KACVwTF~t7$;mptmkaT{y`@W6 zSq3CO8YNBPl92>TxH|gj&uw%EQVUQV% zS*g=$XUb^SiR&y>Q&E15#-jJW4 zgU&|+u0tkv3+pDx4Z#k|AddL_LsoU$1XYu2`PUQ*0lXSS5k9^f)2xoEZ`6hAuj<(9 zceuI3qG*$*SP3q(6bYW+7Q|Uj{!CdUT(An9hKLfgUY_5J&bu!8-LCQP2_n(0r%Bf< zRZ{_Y%&W?q9=KAI-W=~9q9{lw3HhS~`VVxsYXl9X-A?LQa5`FFpZD?6>xgL>n4C?X zDU+n3#~+kEjLT}^Y@6Z8Os*`tO9r*uX$3ym+|?Lh7?hzXqQM(2K=5xoa|_-!EAlbk zOSlJ98E4^`c9pzqz+fne;rKE0y;#D@j92UG3a7dq)!zIVd48c>tum|BXImAfWkdFB zwcuzz^Pc4D2R_6@f~nXfDD1GQSW&BgV+*`NYpwA)FKd6`iROLO-~wephZaGU;vU8a zvd(eH`xP~3{VY0Hm9QEa*j|bzrbynTLBr%~@s>&fmgbDQIQm_}jEmlt4pA7*q&fTa zDJbvxkmfZ&;9^T5xB`X>xGBA?_8!;ziDN37VNuH!18EGg(b6n+f2yB!F6Dxh+{cA2 zLoFKJGH%Z+1bN&bv5AZ$%fva~ib~5ru+=*-YEg(j&=$c{rLcj2@JYUAG7n-AD?L7w z#P7WaZ_94~RQ51U+&T71K}5O`#&Kl#&5!4gJs0e%s%k?f7ASqEYllYItqMjsrKA4& z<8Ynpf%-cBbgTWwea=dLuj!D=?^Q8tIt-NtJeDaWPK%})_;^lJYA$TS?+*7Ekn^!= z1tp`Z?VJ*$nLiRz_ak}P9LNU7RAv70>4kZhOOq_luB-EmNJb11;&wHdaB9#mI(h_HeSabThl_dH`&O+mG z&9(mnsklc+MMaY9Q+lU$uFn#|LR$?Tn^iwiSFWDAYiB@=4e?V^TPZ*Y3G1V6O;==P zV_wnyhem~U@9IqYSq!i6H4}MQxq08GCs{E^raDZu!{uOz{{cEIz9w5674&k=1B8at zh9UOt^mpTqo|Eq^XApONHuXrw%p`Ko@duj8J@X%?6|^{T7WNxj(maplvOs=Ww8+-x zF14mR<&f_h=MC`y&TOJ@7Fx!d-qzpaODDS-&ZHEYtiEaI=pKxlHH)`L7Fs3|%Wz{YNR~ z-Hyu#7O1yV_%EV3VXJ<33GlUu4e=@!eYZv~+t|!=L@o`^_?e7(G{TuQD7p(tti)Hx z8s|amYfccRkchCGaLbs`eA2yR859#%2-QJ{;?<+`+0NvUgP%A)j{9gmsN+;yUBiL-$z+{AoF+i;<=K zn-1xWy3QjiiVi1OfAu*^(X2_$XS+^?rfyaQ`4Go&>-%`*1}Xx{8!4RVY0NjVFyD(P z9p^@%r|+iCA2`Bn-P5}Fdo@qp!W=xGSQ+w1J;M&eR)Us}5^rqx6-}nVowF36%i+=; zP8!qa<@b6=;o1gc_m#cWrDW_{yA1l{TB=>SB*mIkaZ|3N#Xzpjcfkg1vOA4EdA7x& z-KZ7{OZs9iOlX8ryGh$^bSlU9E9vQ>ElG!rU`n~-sVcE1})fs)c&ov4+4-yBe zRKA3V*UMHw{yBaFDvLW7PVLf-o>o1SNTkv`#PcUbbr=9Dn7#+9| zs7rXT&^2bZDj<}APqV~*VvdRv;|Iu*%3x0t^0HsC#=^MrxwYBaya)PmOqR`Zby&>L zj@n2pzV+?9#*e`vS#@c(A4W|`=kH*D7s(xeFbS;D`=#=!p4wD*pQp#%y{CAI8x)+e zv#qZ5ig_vJJabg4y>1oJs4I$;&lw}Bzj4UIPx^WQr4oKlhwp8z^%Oc)VIff=#N)t( z%_5WGG-b~6x3xTt=WY~JOqf~M&LzV_rmM-)B0DdOWQcC^u_COZ-IN-Jn$OADPlDO6 z!2(Fw!W$RkBtPS32Z?kKX(fL5Fxm&9pwb2mhdPodQp9Ti63@i8H<{+_HOtd=LG7K? zLbK6skynfjQkiJh&%1q?vRVkfc<{ZXebTz>&zjAc>_4YkAKBG^gvsLl-L2EPXK7n_ zMwP`*m3unl=g$ESOdKLE4G`YZ*k9hCdrFFj^uv9^;;W!U!FKs3DEI-Ja7|c5!EeGt z(B~t^7wq z_cip~BwYIt6-ClFBdXjSrjvwfo4$UAXL(r|@{V%0P}5;h0pHKjR)c?Ebc_8{+*(uXj@Ghfd^W z1jFj_Kr;pLF=I<%=$rjx<32})!^~F$_LPt@H_rL@G{j?XDWy(*->j^PcV)>DXfh3$ z$LnduC|nxlY{vB1Z53P$n>>6Kp5lYi)#FZ^ey&fDw#28l=RMZ0P=^P_lZ_V8Fb#fF z`{e{NddK~1<@_zi?la6SKNUrip>Dy#c*?t`5DFu9PPQZlulal6=9#mgD=Y`9^H%3CSfE=aP?=C8tZWIsZ02_vus zu#!&aKdnUXV}Lse>02DSY7Q#t${~9=4EPn{G25_J&})k%!!H+gJKw{miFf5EILbgs zWIh4q z=fR`Zn7B$+bXI8^SQ6auRX& z<2U~3M#37DkWx!F+evDyVjiNyaX0*o4lZ&HoTwPl-utY52Ymr!e0_Iu&5Cjef_}P6 z8{p>Bu%L$D?I)%Ndx1!Wkzpa4A#2r-V+=4}Z_eG|XJ$MwgB>XevBkK#CdC4JQj4Gs zW>*~p!tkthCKY#_q62or)UMF{DKP}vtSNY|wxOZ0S_OI_%sInkrrC;gXey*6F~;YD zT)wl+FZxbvaD_11vGVrlH`OZy-S+$FFtfWu|7_`ufafmgg1gV$uXA+-8+=O1f~;UK z6+eZHd;i+{k|auoV2`9k>yYW&+4S1HLaLm88&^V z&3ds|<9r)@Ct2Vzcg#$9i_fuZ)j~{YpjCxywqe2VDoc*QWaV5b|TWufHUIl(WTkPp-^p;IF z=2PnSQM2}~cjqsGWBJxr*@&lu_O2>)uyfe3`hG&chr`)_*{d>hjg@{lV3TXFF`ATj zhgfXD({Yz=-7XvA$-|i_aynIrY^pv={EZ_b{C?DvBu^h*jmRKEL%X{;%CI6(tGBszQ@NvKL zHy`GfZl;W33I2IFwu7{I6j!xbFi+inf4?40<%8QWXB9Q0Ko&&cxgK>bY_I^XFW>ev z-={IsL2=kncKAn$apR7}8rxI@rtIsl6A?98_W90vlv6GQBn6{5c|tw@Te#_8p0ONI zB!)@ti?KHvoWCnd5(G`@Q)L2+;K)}Z97zn(Wvq9{kd$CK^1|U9U?zsNE(T z{7i^vR;^8W&1V-pjz!CxCoJe-qAks-ZJqkKfDv_KI#>EjyF_)dUE4P~=Bc`bpl%C^ zh#|h%5d%3W8dLkNU+^u`b_7mERPOff7Ao0RVBSwy{Nd7;Sk*pteL2~{R?YO(dA6kc z?*l&L=Sm(uHLGRzBj78c*$5zOeD$}?U-Zu7hx6qmZWSuS*4R@abYf08iw3L6cfHH{+H5ylLab~%)wkf+LP$hm@Q_zdSyaQGStWH_ zpINWjPbQzx}>D9%|J@8dC~XgA)`h z&x`v>PqR6`?t3UZW}*9NZ020A9xf24g#>|HFJ-2moc=00xYSI(ATP_LDfX_2qZwww zIf;(pC&K0$)L#U<^a};uR0(hN5P=Hn=zg_5QZ41$ybX1mo&;Vdmf3Zlx6!NWGIn%I z!{SZp`LnC3wCWT$Kr?%4+R*Z2);M?Jf&a3ntfO#to!2Vr#^}|su!tM=PCkM-hhEi5 z6)m>+nC&4sXIa#nI&1Ny+~Fr`h}RxtR^``lF1`UrR0tv-M3t&aro>N`)aiM`45g9u zjhYz?IkEy!@2HCIAcvo$1@0{A1AJN_P+;y%{Lc?76L~2@49TT4vq>+$sG~|$n>!>4 zSN-+oN}rE zT%)NMLzVH+J1U<`r!JQlu(YZgju%o+k{`2Ug>Z+pE0*%t@n)3fkj$?KePN~$i1ni+ zPTOQ=`K))kL{n-)sxSb<# zJ|#(<{BI4}nrN1StGzDuEjYBwHao_NfaP0Xq6U_2t?FD^LoMtlYXuR|RmSgMqqx82 zm6suA^a0F|*ADC|KCM>6_1hwSkEb_A-JL>0?1!=Z3h%^iKT~hjG#@G$k}=(E;wg9a z!8dNE@#^i$@#e^sRvL$@4y$UD$UH)fmy%-t!7RLetUkNsH^TYo?$CS`OG?K<7&BUy zx7V&V@W~ZPOO46ym5$~Y2ghu52_CyxEjyXll@MR0czH`49 zHQ{HgMlt*ss!GAo4z_J~^O#gAAmlvmvqL?f#?4vA@IiHW8q897wiLP2#{<;Q2>Z*f z(u8@}{XfcnnE#5{I1vpjDuaAgx)!QZ<*YwX(I&HSg}JxAJk43|m2I{+won0iQZcnm zo3vM77BNh|;wcHaV>H}|pQ(i9%|T5$jRa+dcv45;o!y43n0tb~hSkq+bvTeLgnj6*Nlg!2tLNowP9S0>%^IWLANU4$t{&2DKkqu7tf z_bs^CqU8KQo|`{LaiQ(@f4^Elc|)!}a$?gFAnmk5Q3-EhYk<*$;)HcqoMJ^-ea|drfx?+oQA$gG_0Hrxj#p1DK_0GRbTsFmWXJj$B(fy}hQaT6?l^ zeTzS6_~+-A^lm5TN~D5+?+slOWI0kvy`DXBG(xRczF^NEdR>gb4#A#P*QVy-@J?f3 zi>V1Kw{iAX$S5Efgu6PkdK6u^?Q!(Z;xmH_n?@la$orXFP3PKi@Pz)WSIc&w8cvBfDuoN>tZe_<&42O=Z~B%Xk} zc=gep!=AncvoWWhrz9_%*2U&&5Hx=r15aZ;#%I10_r-wCIjz5oh-O2aqzYvB@f>;O zr^F?5!Mev!SVN*!BUH`+7iW+X)vuOFlE9SxxK>ZFdnrKp7OzICdeJ7rhwxMK1BGIG z^Fhw>L+50#<)0T)dj*rSTPrcf0$Z;d{gsIl_HnUEA6+9(%+Xn^MWd} zVAbKFl@|zKKAaK1F(U$(1+C&dpG}Akr=c!$e&EhOnsc)@ae6Q8mz=*4lH`Rq9ITXq zr)>IV^_hEPj?GZAV-Yv8AqVplcei^a!IpTa8XFO55Kb`y-}XnFZdz$xso22hiwq}J z#NJ`6x!`=Sj?y-;$#2H8Au-&7zUM#{xcw!Yk!Xww50bCEQ!^;FcI&(vx`hI3fMrC< z#^&Uc3pW0@BzjjHjClmi3Dx`fq#hYv3$cu=MBpR!=zaYp8`p{=4Q>`N*85ppafA&o z`!alCxM(Q5DBsdlm^0P4O4nXNWgn4nYM&Y+4V-`oKIbl)pc7;DDOOTS8`tmo>S6)X zjYW%--k-Uh8!jaFp_t-))6#N+jyaq}{Mbce-nTgv~oH|$gJ`vR0_m_5elNZKXS{=3zBv7bh? zN*XQM&QJ!alfGUVqTx5;=y*CZY&FFDHG;+z1LHy=5}Mj~w1yqbVkZkP$#%15^L<6G2r)u?0WzqoA{sE&;4 zN&Uz(B8IsG-H0f7+bDws9^K0kI`1uKGF|N5Q<$ycmMG@3ZQE6=Y}@84+qP}nwr$(C zZLeBor+PPX`t;6|PI7XSC-d%~7xV6&k7mwyd)b`nAA~-^4vT2E%IUi)0#|&cg z5sP}D1-Dt*Jz#>KpcZ{KXmw&>9zterF^@1J^7@2R;F9~^khM_rLzmYhRU2+JxJqv10-pX6(1B&_KDWzmgv;=K)A+0sc$O^*XfaI)P!-i~< zumUdEYD~4lmWK_N{9>3DJHU0jh00ajvQo@F9k`>~+cxtuYc1TV9U ztMWNdzJG- zBnLSjy;Th<8>ur%Xm_;rt#g-G*z1*G71WhoX|(*_h-QHhc>PLxd=LQ^EMhs{gL-vH z49}&Pk~qQ4$WV%G@A3tGn~keIk(pmr_2%fs3ulNz+9DJgmmP9aJKJ_iVne>2>Xv;Gl-=5tFj-w}XMvnl7wV*-S)kEB=R z;3s-}C0|>2s>Dw;HW+6)Dd{V}M0PmNl!!W+n)+3~$l+7?H`eU-MHDko$nC*T&Pi3GGa8jd{QKb-s=(dGy~)}SyZ4czP#7YYv9RtV&GQIR_l zRj?yjr}Aa4HMA!i;dRf1K;9`4b0+R{?9?*4Rn!r(Bz`sxkYRK1{-)D*da`D|@xz+yj3jDnW*zh9QfCL<|{#|^~YSxMmHP-v5 zyD%xSmvaLt^1A+Jma(Y*8O^3no7Bw){|v|b`B^Aq=FZ5kVr|8g4ddK;mxVuOe~A|b zP~8xj`D<&a`|X`lN)9!f9f`C_ZQ5vyUQM~$DzlqIobxf&0Y4Fou8T+Dff@{SS$suc zzjr3m)IAAG}l`!Z}@aAZE4P%UsrWp2H<{3@EoNvd97R;Jcc?!zbI9E!GK_fFxaCqFo zoK1L5(h|;vMUvEl{0}qho3PR_eLkQnd5wW%5p7exP+k=Gk$|__=25gJKv~zyZ&a%( zDa^R#12;%-EO&!n!5zdkJMwM17LiRmq87Q_FO8HtxgxXc-&D5JQX8EUQGRdUlXhAu^>RtTpO_3*5h1!eGhUj|H*ns+Q)*uA+; zY(7UaLnWs9YE%{iO50Ng%^d%!E+wIBz%CDvnQbgiA8s47Rgtmj^nCgH-LVC62b9dI z&tbEnrIYkLmlGYy3-+5vPz8rgY|!lhgJ(nbg+hWL_rTfzz!d9lI9YfEDJoPm?)Tr5 z<-D{ZL4IeGq8uGWT$sCNnfLe6GsW??%O;#L)ZrE;klKMPx4(_BxgpqK_73;j!X0=Y zYHK7NIxrP`^qi-9+`cyCI>oa<;*V`=rqw2eVd?_inH;crt`rieZUucLzUT?nGEVJ5 z{^`KK^ghUKTc%VSzUr?>6PDRqmai0#YGlrS4JMVr18vd+oTgOENO5sLoEtzqK@5e%?ZGpwdHjO;WeU1(G#tmsMQ z;jFD^CnrV=LF7R>G(6ZXHrT0}EQ4%#0POJVK%;1ASjTk||LHwr6T|{)vIFKsY$|(sZ=@W`V!bj3kCItA=y}_T;bfvk?TyY@PVxqUOiCI)DXS-)INMaAJPMNKaS)infD-f&!xH z{vHP~^0Oh(GzHk+JOG|Uy!xwF!UpM{$As`#wdql}{(=>)VTIticLS7ut-Z4oqQ0?n z?Ki+KJu4eFt7#q=*P&*9!_3NpUTH7Lmi$*nIt&C*lZWL*<|p6j$sWv;)6*y28rYH5 zr>oQo9_4?DMUM>{z8`>V82H@o^Ec_&DO_+OvL0-M1Nd(s>)o3D z-3wd#xf6U(RA^U-F=)M^{*nHE$i0`#*VT!mYr*RNnV6k#IJIUwAfPp8(&Oajru6T| zXI~Ks3IAOZb&gkefU2J=7+YC)C6uG%FF6rs=(+Feu*2O@)NJcMfPP<;FV=crv~NHN zdrwC~zkS<5Mxd|K1gbS$BTx|fk4=&hoGu*OFE>y7uiBGW{Eu&o{O>8k)BUp-*Ke3_ zGn_vw?b!U7f7EVc(Bi(KTJ3;1Jih6`znJ9VU*L1UeVtIYf8hwICd{CyI|g zeRe`fy3i9E8i5=QQ3xaBHK{~bKo#T?Ge~ROJ0tzV5KFsz>1W^D8|=y3i_yDlcL zy8@nGGLHbE#G6o^mye7T1Ab4%1xfdtFy1R?{Pr@<2tyb-%{1AGm9L@dsVGAIk#c&x znCwAbG)Z??tHTm+A1miF4SZ8;BV#D|bHLj6rt`as>r%Lm(U`I8U7(}%{&QpnLb;lO zBMHo$ppBFCD3IdiJwLY~Wj!`Pn)h``70**(+?L1`_JlpJZ>FN?%7`Rl5T~KrzM5%~ zuf*vO&BZaBcaP(crd+aDfZnm~btQ~@E!1DfikU`O)QP;aVH-+SsN~-cw`gLM@i_1^ zJk{@waDZ=n9hradP)@Ol6|rJ>kUAET4aL*L=J&cyBrvjBn3ks~&Z!cTC|)hO#M;P4 zOvM~LcrMRgZ0n@jq5gt1W!AgK&TCXEMEh?HGf}trtoTGX4m5SK7`-#QCP2siD$!D3D#I^!9KEIOMutFPp#QILqWp#&zR z)cm?om%&L7}qNgtd(05S^;8PBn*4#MeLcYFNRTv@ln3!B3mP&)pEWg7EK zrogk_iX%Q6(^LPQu40m<>w3qozxNuIK=8IjlR|e>HqCkz*`9h;s{0mT1wzC@6Nw13 zhyYz;a&j^gscGDLBto-k9NM9eg@op4hg^Psr&m_kb-@wfLWTr zuL|=)2-;@#frGZ5mRW`4fQ%sEa;1%6QF8~gExZROmQH$LmjFm0wr#ne%<>^MYZW*b z>YHApfTEQ;9&nW&j@udvG^ zMnb;;N5fn@n^KUogV<27#MByYH4}>`*-ukMs3=*ch$GnG6H58W8wKaTX+m*2-e8H3 zq_b9OesZ)4BSk54giR`h4)!@HsBczuO1^Z&mxP*i;#&`;gXv5plny6DC@eSg-n%fi zk@XC@GJ-39A7+|=ULrM?>^HQFnwdulQko~QakggEgcpVY!zm14rJBt(;P?Cohsz^a zY6<1Jy;kdnT}#4a3-otFb!Yg!s)oGc5A;w?Gh)aa+#4|FmBAXvB2=HtcqX>5mTV*17Y5r2cF7}?msr%`#eKd1gb=b z$C~4l?|JSipYm01NRMpzGQK>Vjqg6e%4q7;Wyf8U*$pVtmQ+MhU1qo?>LYBDLSL9v z@8FsrBWsQ9jrLVNk4C-{YP`(3gSzaB`pP$19xhbpLvWw^QB9Gf z5RoCi6rYsEiX&>E$eOUl$NKQHM~YA+{v`Qf%fN)8W2OW=?U8B#P2r}SwyL}G!P#62V>$=fNm6ema%?%pwBt={wt=0flF)`A zA{8qmp9*o-uxBJrHnHq-DKab#>)>61yS;=eP=y1CBaT5}L^<)s%enbh+GmP)w0g0L zK}1;-*mrdLyyvHFc0s}FpxJ80HmC#v*waLz*ceBJwxdK}nze^!P2h4GUF<#>>ymX* zl4JKRDLB@`k*E~RMKa8Xsy9ULwrH+>tNuXYaC}!k)%`R??odSUW*skUJie)o7KB1V zoWHfLSIqWP!u&{h?M!yT0Q4CPY{(dsHd2HNnlBMkW3}p4ATFz)hwhQL7jeYvKKZX5%wJYY>sIxSgt&wbrE!xd@!=H2Zs^ zglQL#vw3h!74d1(f<(uO&Ag06t@RnckEXf2z-Jp}T^4QCI~7Z0#g<=I(x8n}8SEi@ zQE&=`JGFTVo~;~*LgTNoP^ks#v4D_MlU=!jA-@^<;@dE{LMkpa*RF~`g`*}Pf|w&Q@)aN!i(NKOb4QM{862O|csd9<9zC`Kn4gEeJ;+VGeD z+Cg^*+>j5Ey0%RqxNGHc<44fmZka~_6)v~8n^Q$H#x~8!^lfM7a#K17Z};^?{(V(1 z!cq0>-ev7`Ap*(_XZH7lPdpDh4TuPSIil2=$gQQ{6ojc^!1Sm5>z-}?9)?*$XdIXi z3`WYaxW{(hwK!C}V!KoPHbcj(9ULaaj1_yMows8oM5crY1__hYy=BD zEHRIyanZx#tm&DrkPeZvl=i~kHlCHLb7-hYFUUE@tMBPSH}zT}zEWVY&8}~j;%bgg za|L&sat}`VO1pK1E>_fqrGo$)hjAK10xygazm2VTlJg5|p=hf@ftqa0<|m3_;QnJ8 z2NK+NG(LACE98E;XKXh0ziZ$57&ffCRmU$fOaU@|TWNx@2I-5kuI+8PA%*Sy<4ZQC z4&?BS(AG)h{?-L^mC~Ogq)#Ud*+pu^H;_fea;~=mZNl^C1Ms z6PFmZ6mK@)m(hIXEav@hdz3lF+r@KX764D_`8@KIFbqj5T%+kUI2}Ujv@&D)-nr2075s`N?qp8Zn-oBOmrH+Tv)j; zZV?$1GjNgpG}!@=z+nZ}!}`Z$Bp62&4Z`=0R|wJ2?)o%w$|#$`0k^emGFZ03CQ`XQ z`uB%)1K6}nih+~;2X}!pNn-DUC-sdIroa28G5b605Z_pGr9vR6^%}*hVN|t^9vWk4 zVFeo<%M;`rGor1Of`M~U*87FD*XZ?&DfFL$bw|OOD?g9rO0Y4-IVW>pS&=9U`&XVt zGr%O^@PSu=i5{j1$8u0DC@}{Lo(mew>~&CFiETzBWz_h6Rq3qrJq0$=7*b)t7l2ME z_k-(5=voqb<^}v|WVGe>nE?z95t@baK{mu0&e89YP1SWru{Ctd4>xR+}Y1 zT;FIvo@YB!%*~@o&z$9nX|t9^RLjLjT5FsYd^ z^jIQ&He0}X`{hF4>u%)rix)~U`|owO#$xpPsKIj!lniiRH@5QeVjhk?)si*l@bRlp`FPTHJDJnHR%OI4VXt*-!_&&FC28>Od{Rl-5VCo{+)Nu z=yk`j9xW?_Ksn4&-snprLY#9EuQ@DObRivP&n11pI7Lc9>j2TvV2u{nj8X8PLD7QZ zs~v{+RaZb=4o?dZxw^-DAi@OpP!#;h%hZsjjOF-vx8F8{%@GdsU6`TpPRv<^C$@uD z_0c6o261tR6|LC`!+>c7{JfW<%n4u8+ZJRJV7MHWdX0_E_=K)xTH*vwe7jhqYWlGJ zvIa_rj?*#}JAm$``qr<{)c@So2aMI;;L{r@+XJgsR5DxKVS8kF;gMNj%5WH9`c zDm+UD8T@$s0Q#drmwm~%*o%W<5nxsklWI9s2D5wK;S*>tC5#`1{ar9-5O^O)Qlgvp)2JaSZ)H;8}nYcGZb14zksa4xzGsr&$p0 zXGP_mh#Fb9eA`6&&8wx;VY4u9#WX;4ZTLAkV;X(oo#(Y~I*vF#my3sWT|DC*SO-Zv z+o))0xg)fiUoA5TV5n|~N4mV?oaCDQbfCdt7*6 z?VpJWASV;V*RBw~eVAT$D^oAC-I7RHrgE!GBx0K;n|N)a$(znxcR8dU(VRF}3Cq9x zhef^i(z6oPQI~8dlqG--hwnpP1(+;PCWB2uTP>J7Q!NLj1%4sLG0FZVwC1Ur+R%bc zQ%?e{*7ujb_DfXewRrJJuVZKwyXNsHffy6 z;zvA3h7Om8SOHHZ#INAe^H zoJNS1=Xhj?yDia9w=Ki(Q(RSURL4mQOhKk9@!TA+_G|TnX$iPnnMLb1Wjzsz-{;1{ zc^uL;%+V;|I{Z4h2q3$@>fDtYTh;jfO*JXqOeJ#8&W%CtS7KuN#$P*UhFZKu*BmcP zs8N_Mf}l5hlPG}9KrwaK#bgPlt66Te<0sO3#c(&}59zIC%tD$a4;&nOXjX!y{z%H= zL2UuR%goxbjjjVqw68C=&~QEVazR{gmqMv-c#ex~hm}X%KAbDGY0yxh7YzTM^tAp> zUwXziW#D}`frzpZ8!1+gft-{}weUOigWH7T30vuJXfRozeDN4c8)Jk3s({4?9=Ye2 zC}{A7^HwZ zgDa(!gIbg>#4)Q7RCq|%2R>Ah)rB9)HwTv>Z1TNgI7fn}D&n^$a?%_Do@pUZoso3C za5s3NbsJs|N^3PqL5vnslHV~&gW$Xpa}R#~N#pclB&)tDAUDz7$h08ASn+k^5O#Q^ zk<3i7hFy~|_YfW6Q*nt|SaYJoOjIMb3xIh98|n>XN&W7lfO}j}*AloHO2Znq@-;UP z@&8luo#3l-_I$~gLYFW_v&gExT{s8tKMux@vEbskNNoBdaqo)2xW%^?oMn)L&iEHq z?we2zKqAp3Ltx;Z;%6^oflk-$BYrzU?dhWo9Eh2hb4Q+&rb5u)TnJDsv0Yw$BVCp8 zY##>Rmu0=kiq;Wm5nBs5>h>`dGnDuIwVdip?RwIf*!FZEJmvjA@qr=~j+Z{>9$ zRw0+$s!L=8K=H!Ihm9Z9l3vH@F$DE1(sYL}s}{Zpue0WX@~+3t(J?Z~9rT5B!#u=$ zm;thTbvDNoC;YYoy|k0mtE0qM`t|4F{F#NRtAW?_*LVr>rqq&epcEn@7&fy4c@>C2 zLb!^^2TO97#0w!{lJL?X95y}F2aayYvJ14S#qttMZmquv1I0iqo6jXoXsxB=F zA?KZoERpG)jCl?Yyr7DSJC4LO#+UGRc}dHZ~<6b+Fts`06|^++@9 z-|)%QVUYr^kcH-h*pk2xe)*+kCwU_Ay8)|f!_NGLRw$!LgwNq-*FYAe3 z>-*n5fMMQ!K*q~a+S2RE*=B$*Td9Uc@w^yLi@f0ClDDy4k?yv9x~Z9v{E1V4jS40i zH2JiIx8{PvJRd%|!%+D{XhpazIKRhVLdOlh4Pd*#4=S!>3n`g@B?;F{?N$i1q4Vj; zHCPSHRYuO7{fzkTJ8*X3v@lJB*FejZ0n?tMnw~Sl z*)sSB^|9Y)-8whyMQ-`?Tx{$8Cs|V5Q%uyzH4p*p-k1@j>$nl4Z zU^AGN>{CeUpdMDtTlB6p9Y8~%Mtk1&F&oH0hOTJBvOy$i%fOreg&)hZ8z#%z&tT`n zpjsgJ32($|Z~f}hMFt(uPYX=*1oYKs`V~lhcVe=yimzf2wIiPJS<1m zI@;kx=CuB`G_F=InVT|(88Sm5dT9Nqgs64;g(eQE4R{|KS`hZ8yY@*=Ym^o*Tg_}m zZL;6`^JR&x-BX%NN%iw5q>f>nBN9p5Zc=g={{bdtAKrQ|V@Q}V^PO!OL`<25@aPgZsaT~}a9e1%; zH1c~Tux=L0LCj2qIFw^}LA1}vp0|Ze&f5B>Z`W~-{*Y^ZCqlxzUHM9Abmx_xEm6xU z?XvibVF&2OVt`MhXcb8N22o4BE{UB0J}ny!O|KB^EsC%XHc71V&W_R>9Bxv^D|by?URqIQimu6 zRPinyHF?p>7SU9AZ9GgUk;}zp#~9oVshm;_S4DtH1-nJ47LT*I+ZqAzuz6<(xb$S#-OJVZcIc=FqFI%Il_~fltfwO`tNb){T3N1=HkZ7S;^_@^pLyqw_`UO@%ZF$!9VS& zNzR6suD>rBA+r6~ic^;oiHcrdl``kk5lK-(cP=r(wb>>l0?g6B5W-=NP>s`S$reTX z)1C%1{AN!T0*h5Ag-Q^bHszvcMWbOvi6+Az2%+pprp@R;;jcqVe3{<~q7Zl1wNF}` zlMf#VFA#U9-rH<@$BJyF-#5>RH^V1SCayvIrIY?F-z;~Q?=@8=H;h&n(L%DFDnk+F zw6V7-6e4IOITDy0q`*qxnS(ASgea-4pZDOGqstF#p6%F)%WMFrvJyNdzuG@nRfUR=R?S5&4eWaSQOO zdoD(6dq}!Ypy)G!m-Y(rNWCL3)VndoO+ogr08q00dy?1 zs>VT7nab5l$Ys*UC;+@an&fe(4pz4B4h9}g%7)pMo%czK(@J#&Lwa^`uFQX~3tji# z=FPOhQ4JhOX;rt-)Xl+by#%a@w=P*>KB;$=sY>9ab zGpSotEK(CpLHQfG3dT=fg=T=CzOCuP^m(*w6*K6e*$YFGaT85x+n+_=O#oi_Iq7X*Mxn z@NwTgd^9w&YdS623}3?xtQK6H+@+YKW}h1{D_<8Ra;v4Q7x+$2Pn8t}#kYqeA*R}`lhU? z4<4SP7Oz=!m~sy%1|xvEYa$V{gFRM0Ge;kvn95}#EYX5(u1q=a=ucEhN{Gi1uJ8zA zW$DcobXOi{f`qB;mW}Vz3{7a09DlQUqh`U8eMmxnpyFIC73IKh#ad8>O4=4zp7?ZL z11>>L7~Li^W)1zeLD@GyF$G>jDHdy4(8k5uTe=7RQSk+MQ4I=T@?XYQ4Yd%p+1$;q z$&+OmJJRQHEd)q0bIyyrIH%}YzhUJI-laB=a~0>3P^BhHJ(4$ul?;gNqSt;_9+dBk z)--g85EQPRL)S?;-H2wDDCG?Wsb6JNkX%EssKwE^?#f%%s+&kyJ8oZs^0)c(`Sytpmynxxs{ccSf<$C^w18GgV=>VZoWdz^_*>Yx*$>J%Fqv^WziiGCQ=g1X zy+N=rC-=i4IJ|#5a7-%HlLY%tAxP@mC=md@z-vj@z@M)f&J$6xi1AnQK)NU=)?U;{ z+re~-gvsfb1Z(rdb9Wn!8Vg)W;GLmbvOp=Is>prrTaBRz;l-7lU<4{YB-xc%BB7@w z*A`E-_k_{mWRYtwxH7}x+IVEc?M0l9qD<=3pR7CyDa9_B?b4REH1zVIno<)%q87LX zB2M3wP@c?BnsPS}6D zC9AV+)E1w*DeKdCi+&c#ln@VN*3v_;c_fVfH$aoqP`S+oieabFMKv(3S5{y;~jEeDG671?-FL#Ib$X6p9^il@)d;NhOe$ z14FIkBdvphX1r0nlc+wb95HMKqJsRv#Y?{R>lHJ#w%qctwWP4;ooiHqui8&5SCVn| zu|LGw`isBGGfQ>=JM{LEpdf>VXM+(>ry|`IN;bS{hB#>cY{O_q7S@eK3!4#~(|8^m zRkLjJs~!h-Y82Byl3nb}b({D7ElL|Gc2L=%1|I}i^R3;ALXMJhfb|xB#hDcrNoE^w zT4H@ie^8Bpqs5BX78y2&Y-yRF-Gd}BOe=b+1#g~f8CO3CDYP{a*a_ji0@VZ!fKZzW%QChbORhleD2(?16g(}Ulo zNLC;oU7I%<3$)N4txtJ*k&@g-mrcJ7d(q(|d41Ck%$_XseN`^BQA*&Hx2k?U7UVzi znt=H`L}^MT0Xy=EGW<4QP}DZNfX912?)xH)xuLO~&(?b@HUroyiND4lSu{PTH$>B)BSFKECk@R={$4jLY0Rv=Z4rz?9{!G9VGyID#8Q-F!HGmAvmjh&N?-8JH{*WMaZ<6TTOLz5+7&U9Ei&I3OSylYWrBFPISFtHo2%- zhLX{oOooHHx)cc=z7wlvSq0l}KJ2rgWhQJB*XS~);?l1M458ZwoZk4eYfK;7Q3`zw z#P(0%ACx`K5NM#DGS15{_66H{04MpR#p5dOaR~SgN{*5!Zi1@Jw0@Vg!)!u)&ZHb~ zhVa1`SC&iFRE@T_zcV1bCPCtF5~*2V=SnaB6lV}_y=cCV4>E@@MO-s*>B`!R2sUVT z$Q>lTY>lPDtiC2CY%V+y*1jFmhjK??jwf|lNI}Lo1NYXz$yrQsU#r5nS|>2~fI3yw z!LDe(!Utj!&&-2*2r(=glk0pSRdjWl;H+BCnAU;|;qxKVqpb?xmt!2xw?OE3W@H*l zD@V#m*}jxs7C`k-gWSFtba=l{g9iuj4yTjjf+D`X7 zTI`u}=OA2%70o@-4xAhv=NeT?rZlK78?QIursJ1av!YCFQ{=7O7V4*FvfQdP`>t^A z%_^77wFX1LUvJ7#j^g`+6ajC7hR!TWXJ9nNsm}5u7sw~tTph&o#Uz!o7inZ+RAx<$ zX=AFV*q3ml!IWCl=;H;Qu<5qw4tz42j1i!C`C>#B%i&vv8liWgZsGl|_YGC;c|2NH z_U@3T;k!mB(+D=S8brv_HO6VNon9rfGNI(6+ZN5dd7iNKxEZL)W>6?c!oK5S0}v2I zsV8T)>0)sl<0<2QpQ}^1Ox>1jPzTb8CH#faQllCHF}XSD(?^d94y|l5*s?e z7chX~p9dgkr?wMdO>S5Ce{tb@ahXb5FfA(e`mAGW>1t~*G4@9T>llJcQl_=#&{dCy zIP%jk2aS79@`lDl;R=1Q%PQs481Kaz=MJ*NOAgB6+QyO<`j2rhlgx1T@jni{8 zaD}H(H{40XqnA^Jp2I;l33LXJoqLpNmtmS|%I!RiLY%Z*1u}jOoYzSAAs0@L;p+db zEgi1M21A@3yhWU26yQyZ<13;$z?t~Px4FEz$DOS+o%U*;;-D|Z6I~aVNSi2)Gq)OA z^jU_BF*JVHyTyh;*+rEtKUshjotpP;!?BO+|JJk;S*6ii->-gI^B~rhM~?$a+(%wC zRdj5CWs7vm;JgDhy&#>2yH|aRZj|&*`!uv7PAlvw<*MOO_qkq!7NHyZ9!xlkpTif% zInwAq?TGln(#2NtQl**2+jg({X{SOjhcu;Rv>jvrGV5Ja{cHFe7<=DI{GU}{GX9t9 zO9FOQ)_)B6{O^qT^j49vEu{ZhDL~y7DtS#94ZB|%L4*mR2-jJiVbQ#bl0~Zsj7S*z z_5PTd-qcxH0X}#CGW{|=xTdy?*TS?3H*15oZSi;}JLqn;RrS%s1*WF9JeLJulk3C2 z+VRBb*`Wk~IA^t8R5K~leBOCJlE~(L?_skY{K!#mN2mQQ(9e1CHvXB>UXtll^H{|( zC3{+kZJL;FUc~LZSXt`RujnJ`&01X6C&!l4k>9IyqH1{!eA)9W6c~#O)Df}t1ik&e z#`b6LUrs(7eZEDfNdjVbW}qv?_-5uWtW%cjjUb_ol!@b}?v|2$;=)Xe&+2*6><)}IV@r;jt-l%$5VGLf<7{OSp z4`Dnuf*!KayIYS%(&$IMsZXS_V?M*frn?DTCZL-D>NW&0Fq@JZx+SSZmGeH@8eD}} znZRUoT*lVh%)A-?aa@t}q%i$tcsQ!!%hy`T&3n#zEFCiaguGKXg_`ELl)j60T)|YJ zy^Nwrb;f$DUo(x=sd|aRtA4=&j+`Ar%&kvB&8?@aeu;9feu3euev!tx!|c|)!+iUB z%vSXhy;b!Ju~qddc{^Zw`1zU>`VR9F?SSbe_yxv?kXJ~KkXNbZC29xyF8$?z>5hpP z6Jk<5NC)Z;zZ>O%?@6EVOG)NSjb{1iyC{$IfR7!u7IiQ*;?O<_Tr6=4@ua;X1vx^N z1ZTO;Xy-QIggfRdBQG;-#yjgp`*w$}^3YBgjmK$Hn^q(0=5h~SZRC{gpOO73f&QO{ z_x`UXYf}V9BnbEUe96Z2Psl#~8`+spW3zvd1yxg1+0}}v!S?+j>yp8C>sD8WAe5pX`BD8FT_H^Du74M*H7Kai+ovNMn}9|zsqFY2P@6%m z!lH0iWSc%L=zbD=1~0-pRzLFKBe5C17N5i)JuRQ8b15}^!-~eQNIv`jpX~p2vWzhQ zK{j-zTUWL8D~QJ9EG6^b$Sy{m+Wa%J%>Q2@8@=MOf@69*IS$)6KgFz=19$kJqihzu zw`=KCb?&pj^J>Z`Vsv(3dzjL_#Ld@Sb0CHC>>PHQd~>Kt(od8v{700%Ka0t<*#-aC zD0|P|^{-L3$%fsD?&6KfKc}ucOSTryAPG*Ddby*!_R{;co{N*^yWrCGWN~x6JZ>a} zh1L6Vrd0bdMEm(5pm>G4x$077uj8qg1KED!-sj-|j6$Cly)H1aT3GjZ(kKN!DNJPY!>@co zWI8kLzXLRTQmXVJCsjr;u?-On%cWm3Q9SX>QTa?iadl-{-517Z=FJ@brx8CSC+m?s zY4Q#EA*b;x>7|N~koQtLpTjhpvd4aglX${78}Yb7R)`Pn-D*A(HFv%cHP=w@E}i{= zX{F(S>8bc9($=8n?w6wGh7EBB^`hqHFGkFsZAZ-hnU0uM*7XYU`T5$Ekk@cq z&nQw2?~Y=VYHIVrlCBLqv-W%5RQF1TpRwlYHeLsO z|C(dvo%(c*`DH(}yyh6BblF7-=Nl?tB@d?G!Q)Tk%TuMxDrjRmR`1-&KD$>#L#N&c z^OB9(2Ai{|g2>t1(lb6BR1(~Yqe>?(%g?J#HqX+LRUz#bT|UoJK^<%bCFf?Yhs+7s z%%^Ekrs`7*)s*|b=Evon@M5D*u2Zt4%2Kzq@O0!lVJ$}bUl8;u28j#AFo}?Wgd3q+ zrIcghU4U?e2D9C9^?^v(H9!`$4@b3q-6f>OOZD{;$T2uic04g^U8byfn_z)Ts2Ay5(+j6J~*_6 z{5Eb-qdgHdrRl)!KeOaW$oF^~4R1o#9OX&-lITJEAi;w^W33TpyC<1WJ;O2TtGG7- zbr(Hb=61%X=KJ<%|G=SL3w+res#iMHdT4|vFw0v*XHycxw4e;1$vv! z;rBno!216j!#{3`s5?T%Pd|U!{L-i*LLY{^#=p+4~FZ}yPvzE%PweSa0c)E$sMy-T|=hc z8~wjvP;>VF!SJ;8eETmLQlwNmd44e5x`LOM?1pw&@%cW;26wR*lw4T2Ub!Y?yIfa7 zqiN7B7to~oTVC3FlBFc1ex_y0*W?`Po0v%SVcC!ObrJOaj*yXzrI$7V2{OZX$Sz3( zFd8ZbNbwtz=ROtX6i|w^f=-c_TnIaG8u1$kBp^K5r+!BG zX6G(#XFdjcQtDi-Wrs|>t-NJi0r3A^Ze}>njqQM(ZBy%=4M} zR_jOmitF@87Ru&Z8P8%peEW9`-*=^DX~vX=+L$l5i|akP!oQ>rI+^E#_PvFWtz9p1nWu~OEN6ex(_umGbALp zyrbiIt=IYYE0*(lGTLA>HGc^g$ z(-N7}D&?lQSd;*eSOEnh6~YVzNz#4|6e@HP6&wm(T!GFwWRNzFP~MJ!Slbj-!AVv5 za~;0Xg_79n7piGcAK@xnUmUq}4)9_RrzW5yz64~J1MMBtNogO4a{*FlvI)#IRe-62 ztec2^>Pa1-D#Jrr8bgNaadKh4dMK9tG$#wQ<&&^bW(ckZ^#JB5Wvcf4nwsM@LOUQk z=Ug4#7zg@eV8G!VzM_`GXPiSSriWGyJ09R*sNcKxNmRI!9UWAg5e_@f=b^7~7KLC< z{1xE`^TkE`{AwdT;CpOk5EDJ(J2Y+x&oP(Di$^~GV2$Sj?| zjS_u`SV*^2o!ZoCMxHFr-<~=2#vG8((MGW{%*Gr57Q-GP%4I?x5kH-uEdG)d_j?Re zq{LuD9{c-PFKn)T0F!K*oN4Zir+`q#-}ZrBrxkb~JOvv#pYb-WXpG;;U zCu|@#6?2=JxE?oib@xttz?OGK*`fY5<#w-caOrhvS%NmTIFcAq98?bbxyfs z8aIiOJ!dCsZRB5Y#hEksflCc=)oH}iR3fC;w(h8bLRFTQeKpxPV z!5EZ7lfKhf@<8mou5CH@5*%kpAg|;E_inB4a&BlOQDw3CRlPQDma#-UHxOwaDVG z!4!y2NeZy#7Zll0G|~T+1*&E_M}s5biIjY%jL1@ErO)u1ISnBRxgRAjTD$Ty1gs1I zUS5wK6%%zY42W!ar)UBUY9>i?K%kf+E{7gPwj#_R#XPT|PvL~1Y!6fxS(ASgQ32}f z6tx*f?MYqI2jCcl0=ExQz9COCet=@YMG;ybKnukWwBv{xGkJ{oj~11HEcw{|I1)w> zf*)U8FQpWoY*LwYd;el7u+QIDDtaIKOqFXteBIa$x9^6uZu~!k6*)m~$^`#gW7ioJ zRnw)B93%@!9?41KFyoM;4mnEB&J0Kz7;+d8kPH$9B)sH|fPf%~1P2f#i{uQFBnQc1 z=iP6=+OIa$ZdKR4x4X~jKKIA1K2P7%=K*#z-o0yI8rFulG9EP9&ea*D^XM^d(cEjv z_bxD~GvPLpiejxT)nSxJT$lbXvb%;{xzbdB|9uS4_=rJRFc|M1w2%xQaMbXwI@0i9 z?q0Vrt*5B5W+Hr_udll4y&WlJq^~a_zkr!&McSTzD62GeUVmZ5a8Y)DkoFWqiFYlZ zX-SMTg$Fj0>XT`X&`C8m-wmb1xLc*Av<`mGw2J7jxRuE^`Xd_Xs4UyxzkK2ka*cHa z4Qxx@$+Ujoz7}7azw5x|O@8oFkvZ2yart#X^L(f-eR{VL5>v~M*nk75-g29gk{|3ED2{S?$-lO>^WClUEY| zy4@%q6E-g*b2+MUT)D9szlV^=SUBWYPE=|#X-Eiw0F*igPcwlJ8)?&{hzW0RsTM_CN1-b zD-8@x8Xi){9P8~J&6!b$f;duKWroHy`9w-qqxB3#P!O$R-?}}E^38nf*r)4|>1igyJ~gF|fE0;EG={)_l4Y-tJ&n)f3vG(#LR zA@;Z!If69pA3tx{VN?SR{`QHI)9|AH*CYGfu}Nt>>1f!;$!HB1`RM^3tFtjh^GThd zSG~Y(-q_w^c=xR`zkEz(@f!K#l2r3HJ@=r?#8-;+t*oL$C!`OzUM%_Cb8F1S%;D6| zg5Iwt+qtLoNcKjICnSltE+};Lqp1%nf_tm_go&8njo>yURH@1xF8oRER-&m-587qo z+IMEK-rk$!uE`bjDWB~=_)EpEu1&3IHVYbTZM;A}ND=v^|J!6_H(f)3a=n<)51y~+ zyk<-0uR=9B z)^c9yQzQRb0n0S#%!v7vzz2mf^`LRTeOT%n1KQS1gg5X{N}x=mQL`vr3JXsbwfG1k zN@H5SYZBH@)kyiayW+XSpUt|S^DebX9V6+)a<9++lpXfY8{XU2R}$TEr@2`7CXL%N=J3?$n4dAeeb$c=;4nsd0y!WgA6C;p18n-*-efg^R3^z7NQt5o3JWY(Xb`wF8M$^=(kHQlw zHnLGmu-Obt6O#e26F$C#C|@#JvzgBOfs#zRb9HNw~Y5hHWF?*3!8Yq2sfEz;1 z7ZeaOF45z4*_`=QR$%PWy3VS%RZKHW-w)ci%@@62))E%RP8s?}^0%DsT`J&2Vw{Ck zt>@){Xhwxaoy4627pXL_HqWYGqdmX#a_0$n_mdktEWzip+;OsR390`ulI$SlgpMc~xsTV`oux@*`S$bM^Udz)1Er{dM4(iMgHPn??aKA{?mYB5 z9mH~D$As>rgE}ZLByO|YS86WgKYW#6Za-YarXXP*_FFijW*@&eM?3V3x}>jVGOfIj ztN!j7al8f};*~j=x<{Fw(%QVVjPbHM;?JdL$IG<~?;O4$Xg?41qDHhX?fdnKT*M8> zm@d)PxV5bqH%!t7#1#9<8|MpGkhBZbFyAh>IAX2~DXfkoz{{LwPBA*I{N-b5O$=Vd ztv8=nCa*c~vVvD~A?T=M7{#96Jy@#Itg)%|roY$GYE5uRb&B+60lJF7+O%beA+WgL zq}37yQV}pM)tU=yV85Wg<99u2wV*nWXx~lD5IH9kJpIZtc9qf+K6otk%cTriZx&H9 z9XSJOPSI<&Ijzg6M1j9eN?aB=T8uAKv<#v*gAV&Y+qbv9@iag!C^os}Nf+zLCOyyb zwvKcyxJ-whN{xz{F(>K)_d73K=y{);uK&q_$#DUFRP2I-eq0?KU!HFstbN|MW=JpG zUVoDNg@w&%4JNI1!hDtpI_xOwu~|kse20~K3vn5~9 zqm*EFickHB*aZa|^V>L&->>fMg&pq`$$ycN+giB>Wn1rmoyGy$li1%sH}Y{y+kVpT zmHmezJ7>3}>STU;KRxF{1lglHO25W6Cz%>w-7g;Ku5yK|b=PLSM1`XvAn(jLb*dAb zbq~?Sb12<7Z3wJ`nR#Q=N}K;4h?{bxl|bB^!%Yfz@p!LROiWxZ3kGmJT#oBemDuoo zcAYjM;$~Am&-E9)v0v7sLPP$zP6Kl6AMf?)C6U~KKEo&^0b0=5|B^sJUd3_7HBhYl z$D5SVvvV?JfldR%Umg>bejlmqXR3OXO2$-A7s9W!77{Rjd_^e^N!$Omcm zHR6u1=P}dyi=un$k!=s#-{FkfujZ)f${RK8_jFD(_^gXbv8-!FwSRVgw^;+9J--N? zV{V@|DYbR@_(AyX&Q2Bb`hGrrE=9Is`ylCM3u98X|4mSB^;*yMX=56;%v8O6DQ$*8 zH~c)CM0uEboA)~__j~fDRVSpa-NlmIhX$mzQ_Hei1@!l7IDzOVtQQ^66W;+ zOKi&L216LaO&*I014V_!!Th8mie8=$NE9Exh~Z05XE^q5;AQ6sf8mLJQpPf@dSd%D zZLulBv00%7gL80IXei040pAQlR^Wejd@$Z5!k*+FO(o_JSij=shI7k#IE-EG= zE(sJh!%Ao&VgGxlA`G5)`qUH{1+3xxd37RY}^coN`$0z7fZe*!!)5H``*{}bTNo6Ne-fvC`nHA^on zUYQ$pQw+sb9V>E}DFyI-qH&>}P$!G2tG~D$f~IQjI?l_{x!_=uBeEsztA7eG za6PJmLiMPGJwCx-my*np6`(khU-PM{WYWH+>U*HJoFJBr`Xor~!1tF+5!X9tv?kPu znBzg8UUW*R$Jcv6TSfp)PX>Tx=YtIQt8U-SPWi9+BE-%qYIPx&G_U-=6KnX9f@`Z$ zHmRgvQExPAOMB_&I_hvVSfGc_Cb-gm5j0CfOYAyi9QcADmh;$DOpBcgj0X86iC=d^ zovIvPohwNdm*qLBRj7wW{^yD*3u|O!J3D2BbMUxN$*l8YBYHJ@rlVfMEwWRi7n!v? z-1xh9BW{`2SGY$|u&1*3n_w+tbq8IwH3$q&*DOlcw4ox33ys`%2;)Vyil6#S#cA32 zEtzr&O0fr6&k~LYHB=v~ya6saecA{aBhDC%nV5_yv$F_P2E4&oy1_l!*>e24a50g^ z2q~wd=Yy2%mETBZCt=vrd?G;JlCVMk)Iw!}Q|M!vfIpoyIhs>nfrbG}_tpZOQbY(r|8(r7nn_pw@wmx)n~6Sg#r7 zkl}=09|-q=QwD+`Zc``?7AFya}A)LQsX|wGa4bFjb*tfazD_ z)Ylm(gOMh4#B-B!3nT+5>5z&{0t+Fv0@7=eS=pfXDN2L_ z6a+N7?p)&cwOFE92!v=J@DT@XS|jKL&0-o4=`xeI%|N2B;j-{sZ!E#ADk zJJ;wc5A`BH;`*sHFQ;e!nW$=#O7~aDuAJA=c{U*9Nqm3%*1F~|p(zY*;}d&oxU>5^ zmC*cdKdz3(LBqc<_0U%?=Dh|rya8i>c_~36AUuY7HsTv*i)k*b#`6t7_Z`iD^K0qy@{a`!vOYgLe4tISn%8$D z>mAip>rxxKONAT$5l5@~5p$D`LlsAlGMyI!+CzzXnOIcmey?d2ZU2G9iTDvcmI=j~ zzw<#|F>y+Uuqx)-ItGw*U1h@AKiU(E|LAP2Yv*!ah&8I74lx~S4dCw^)E z*f)nfTQniXAz;k6eXsXrEk49XVtnY=51w|u_2rLSj2+w8Bx*ZM(J{qL);EbtZlAhJ1o;Qt#(>e! zV}sBXV_7y+m)!46WMP4gJ> zu$wC@`0X8mX3_KM|H%#bS6=jgtZOFva0ID{nkx+M3osW2h=U-Oq#_0{{jsYh2uqM| z3@{f1NC3pJjrvHWC${nLUuF7#Yao!=+TVV~-!&D0xr{haLIMntRDysZV33NWxTvHm z2>cYN3<4@afMAuUa)AF^1Ut_^_}T#=;D2nG{y&J=xl{X5#EK8YuX5cL4rAuM$cHAK=p)Ra6>^Eq=K8ohu&z@>DC*s%jR1FF!nk;qW2 zEUh3g7l;x-u6>};jTX2X4ST0*_)dgWq>I9nYS6$hWyh*?q;wTn6?|CXNapCeKuZ$*@*cl*`@_qII=YRv9QCX`xJVRE~Hz5`OlTb2_0Wpc&bsGR6ifm7IXm=$q(~-uy=b90HDn zu9?ndk_Fqv@|+?o&mP!kqPc!4xKMxTXIuHz5JsV3r2_iND0Vun+#3sxjtXz#Ectd5 zI?~&dLX$*eTX)HkHX-wR#6fhW&sct?;?rXw+u_N3$!j`rn5d~Qe>Qzd)VAVjwua)= zC}q~hWdPat4YZf;ebr= Date: Tue, 21 Apr 2026 09:33:48 +0200 Subject: [PATCH 22/23] gas optimization - max approve in constructor --- src/helpers/VedaAdapter.sol | 32 +++++++++--------- test/helpers/VedaLending.t.sol | 59 +++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/helpers/VedaAdapter.sol b/src/helpers/VedaAdapter.sol index 2f9c8f9f..d16465a4 100644 --- a/src/helpers/VedaAdapter.sol +++ b/src/helpers/VedaAdapter.sol @@ -42,7 +42,9 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol"; * to itself, and calls `teller.withdraw()` to burn shares and send `depositToken` assets to the user. * * Requirements: - * - VedaAdapter must approve the BoringVault to spend deposit tokens + * - VedaAdapter must approve the BoringVault to spend deposit tokens. The constructor sets + * this allowance to `type(uint256).max`, and the owner-only `ensureAllowance()` function + * can be used as a fail-safe to restore it to max if it were ever reduced. * * Leaf Caveat Format: * - The first caveat of the leaf delegation (`_delegations[0].caveats[0]`) must follow the @@ -188,7 +190,8 @@ contract VedaAdapter is Ownable2Step { ) Ownable(_owner) { - if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0) || _depositToken == address(0)) { + if (_delegationManager == address(0) || _boringVault == address(0) || _teller == address(0) || _depositToken == address(0)) + { revert InvalidZeroAddress(); } @@ -196,6 +199,9 @@ contract VedaAdapter is Ownable2Step { boringVault = _boringVault; teller = IVedaTeller(_teller); depositToken = IERC20(_depositToken); + + // Approve BoringVault to pull tokens + depositToken.forceApprove(boringVault, type(uint256).max); } ////////////////////////////// External Methods ////////////////////////////// @@ -307,22 +313,18 @@ contract VedaAdapter is Ownable2Step { emit StuckTokensWithdrawn(_token, _recipient, _amount); } - ////////////////////////////// Private/Internal Methods ////////////////////////////// - /** - * @notice Ensures sufficient token allowance for a spender to pull tokens - * @dev Checks current allowance and sets it to max if insufficient. - * @param _token Token to manage allowance for - * @param _spender Address that needs to spend the tokens - * @param _amount Amount needed for the operation + * @notice Resets the deposit token allowance for the BoringVault to `type(uint256).max` + * @dev Fail-safe function. The constructor already sets the allowance to `type(uint256).max` + * the allowance should realistically never be exhausted. This function exists only as a + * defensive measure. The owner can restore it to max. */ - function _ensureAllowance(IERC20 _token, address _spender, uint256 _amount) private { - uint256 allowance_ = _token.allowance(address(this), _spender); - if (allowance_ < _amount) { - _token.forceApprove(_spender, type(uint256).max); - } + function ensureAllowance() external onlyOwner { + depositToken.forceApprove(boringVault, type(uint256).max); } + ////////////////////////////// Private/Internal Methods ////////////////////////////// + /** * @notice Parses the transfer amount from ERC20TransferAmountEnforcer terms * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes. @@ -363,8 +365,6 @@ contract VedaAdapter is Ownable2Step { delegationManager.redeemDelegations(permissionContexts_, encodedModes_, executionCallDatas_); - // Approve BoringVault to pull tokens, then deposit via Teller - _ensureAllowance(depositToken, boringVault, amount_); uint256 shares_ = teller.deposit(address(depositToken), amount_, _minimumMint, rootDelegator_, address(0)); emit DepositExecuted(rootDelegator_, msg.sender, address(depositToken), amount_, shares_); diff --git a/test/helpers/VedaLending.t.sol b/test/helpers/VedaLending.t.sol index 7930e744..d785520a 100644 --- a/test/helpers/VedaLending.t.sol +++ b/test/helpers/VedaLending.t.sol @@ -570,11 +570,21 @@ contract VedaLendingTest is BaseTest { assertEq(BORING_VAULT.balanceOf(address(vedaAdapter)), 0, "Adapter must not retain any vault shares after withdraw"); } - /// @notice After the first deposit, the adapter grants unlimited allowance to BoringVault. - /// Subsequent deposits reuse the existing allowance without issuing a new approval. - function test_allowanceFullyConsumedAfterDeposit() public { - assertEq(USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), 0, "Initial allowance should be 0"); + /// @notice The adapter approves the BoringVault for `type(uint256).max` in the constructor, + /// so deposits draw from a pre-existing unlimited allowance that never needs topping up + /// under normal operation. + function test_allowanceSetToMaxInConstructor() public { + assertEq( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max, + "Constructor should set allowance to max" + ); + } + /// @notice After a deposit, the allowance is simply `max - depositAmount` because the BoringVault + /// pulled tokens via `safeTransferFrom`. The allowance is effectively still unbounded and + /// does not require re-approval. + function test_allowanceRemainsUnlimitedAfterDeposit() public { Delegation memory delegation_ = _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); Delegation memory redelegation_ = @@ -594,6 +604,47 @@ contract VedaLendingTest is BaseTest { ); } + /// @notice `ensureAllowance` is a fail-safe that lets the owner restore the BoringVault + /// allowance to `type(uint256).max` if it were ever reduced. + function test_ensureAllowanceRestoresMaxAllowance() public { + // Simulate the allowance being reduced by forcing an approval from the adapter via the owner. + // We can't directly call forceApprove on the adapter, so we verify the fail-safe restores + // allowance after a deposit consumes part of it. + Delegation memory delegation_ = + _createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max); + Delegation memory redelegation_ = + _createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT); + + Delegation[] memory delegations_ = new Delegation[](2); + delegations_[0] = redelegation_; + delegations_[1] = delegation_; + + vm.prank(address(users.bob.deleGator)); + vedaAdapter.depositByDelegation(delegations_, 0); + + assertLt( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max, + "Allowance should be below max after a deposit" + ); + + vm.prank(owner); + vedaAdapter.ensureAllowance(); + + assertEq( + USDC.allowance(address(vedaAdapter), address(BORING_VAULT)), + type(uint256).max, + "ensureAllowance should restore allowance to max" + ); + } + + /// @notice `ensureAllowance` is owner-gated and reverts when called by a non-owner. + function test_ensureAllowanceRevertsForNonOwner() public { + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(users.bob.addr))); + vm.prank(address(users.bob.addr)); + vedaAdapter.ensureAllowance(); + } + /// @notice A 3-level delegation chain (Alice -> Carol -> Bob -> Adapter) must correctly resolve /// rootDelegator as Alice, ensuring shares are minted to the actual token owner. function test_depositByDelegation_withThreeLevelDelegationChain() public { From f0c77d19180be6a7831dd050dd4066372d1d275a Mon Sep 17 00:00:00 2001 From: MoMannn Date: Mon, 4 May 2026 10:01:34 +0200 Subject: [PATCH 23/23] add monad specific deployment changes --- script/DeployVedaAdapter.s.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/script/DeployVedaAdapter.s.sol b/script/DeployVedaAdapter.s.sol index 1fa72062..52eece91 100644 --- a/script/DeployVedaAdapter.s.sol +++ b/script/DeployVedaAdapter.s.sol @@ -12,6 +12,7 @@ import { VedaAdapter } from "../src/helpers/VedaAdapter.sol"; * @dev Fill the required variables in the .env file * @dev run the script with: * forge script script/DeployVedaAdapter.s.sol --rpc-url --private-key $PRIVATE_KEY --broadcast + * For deploying on monad add --skip-simulation, because of how monad works the simulation fails because the token is not activated. */ contract DeployVedaAdapter is Script { bytes32 salt; @@ -43,6 +44,12 @@ contract DeployVedaAdapter is Script { function run() public { console2.log("~~~"); + + // Foundry's fork mode cannot interact with mUSD on Monad (NotActivated in revm). + // Mock the approve call so simulation passes; the real broadcast executes the + // actual constructor on-chain where the token works correctly. + // vm.mockCall(depositToken, abi.encodeWithSelector(bytes4(keccak256("approve(address,uint256)"))), abi.encode(true)); + vm.startBroadcast(); address vedaAdapter = @@ -50,5 +57,7 @@ contract DeployVedaAdapter is Script { console2.log("VedaAdapter: %s", vedaAdapter); vm.stopBroadcast(); + + // vm.clearMockedCalls(); } }