diff --git a/src/interfaces/IOnChainAllocation.sol b/src/interfaces/IOnChainAllocation.sol index acb795b..4561e5e 100644 --- a/src/interfaces/IOnChainAllocation.sol +++ b/src/interfaces/IOnChainAllocation.sol @@ -3,39 +3,77 @@ pragma solidity ^0.8.0; import { IAllocator } from "./IAllocator.sol"; import { Lock } from "../types/EIP712Types.sol"; +import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; +import { DepositDetails } from "../types/DepositDetails.sol"; interface IOnChainAllocation is IAllocator { error InvalidPreparation(); error InvalidRegistration(address sponsor, bytes32 claimHash); - /// @notice Emitted when a tokens are successfully allocated - /// @param sponsor The address of the sponsor - /// @param commitments The commitments of the allocations - /// @param nonce The nonce of the allocation - /// @param expires The expiration of the allocation - /// @param claimHash The hash of the allocation + /** + * @notice Emitted when a tokens are successfully allocated + * @param sponsor The address of the sponsor + * @param commitments The commitments of the allocations + * @param nonce The nonce of the allocation + * @param expires The expiration of the allocation + * @param claimHash The hash of the allocation + */ event Allocated(address indexed sponsor, Lock[] commitments, uint256 nonce, uint256 expires, bytes32 claimHash); + /** + * @notice Deposits, registers and allocates a claim via Permit2 signature transfer + * @dev Deposits the tokens subject to the order and registers the claim directly with the compact, then allocates the claim + * @param arbiter The arbiter of the allocation + * @param depositor The address depositing tokens and the sponsor of the claim (must sign the Permit2 message) + * @param permitted The token permissions for the Permit2 transfer. Must match the commitments in the claim + * @param additionalCommitmentAmounts Additional commitment amounts to allocate. Allocator must verify those tokens are unallocated. + * @param details The deposit details including nonce, deadline, and lock tag + * Nonce must match the nonce structure expected by the allocator + * Deadline will be used as the expiration of the claim + * @param claimHash The hash of the claim to register. Must match the claim hash recreated by the allocator + * @param witness The witness typestring for the Permit2 signature (empty string if no witness) + * @param witnessHash The hash of the witness data (bytes32(0) if no witness) + * @param signature The Permit2 signature from the depositor, will be verified by the compact + * @param context Additional context for the allocation + * @return commitments The lock commitments created by the allocation + */ + function permit2Allocation( + address arbiter, + address depositor, + uint256 expires, + ISignatureTransfer.TokenPermissions[] calldata permitted, + uint256[] calldata additionalCommitmentAmounts, + DepositDetails calldata details, + bytes32 claimHash, + string calldata witness, + bytes32 witnessHash, + bytes calldata signature, + bytes calldata context + ) external returns (Lock[] memory commitments); + /** * @notice Allows to create an allocation on behalf of a recipient without the contract being in control over the funds. * @notice Will typically be used in combination with `batchDepositAndRegisterFor` on the compact. * @dev Must be called before `executeAllocation` to ensure a valid balance change has occurred for the recipient. * @param recipient The account to receive the tokens. * @param idsAndAmounts The ids and amounts to allocate. + * @param additionalCommitmentAmounts Additional commitment amounts to allocate. Allocator must verify those tokens are unallocated. * @param arbiter The account tasked with verifying and submitting the claim. * @param expires The time at which the claim expires. * @param typehash The typehash of the claim. * @param witness The witness of the claim. + * @param context Additional context for the allocation * @return nonce The next valid nonce. It is only guaranteed that the nonce is valid within the same transaction.. */ function prepareAllocation( address recipient, uint256[2][] calldata idsAndAmounts, + uint256[] calldata additionalCommitmentAmounts, address arbiter, uint256 expires, bytes32 typehash, bytes32 witness, - bytes calldata orderData + bytes calldata context ) external returns (uint256 nonce); /** @@ -43,18 +81,21 @@ interface IOnChainAllocation is IAllocator { * @dev Must be called after `prepareAllocation` to ensure a valid balance change has occurred for the recipient. * @param recipient The account to receive the tokens. * @param idsAndAmounts The ids and amounts to allocate. + * @param additionalCommitmentAmounts Additional commitment amounts to allocate. Allocator must verify those tokens are unallocated. * @param arbiter The account tasked with verifying and submitting the claim. * @param expires The time at which the claim expires. * @param typehash The typehash of the claim. * @param witness The witness of the claim. + * @param context Additional context for the allocation */ function executeAllocation( address recipient, uint256[2][] calldata idsAndAmounts, + uint256[] calldata additionalCommitmentAmounts, address arbiter, uint256 expires, bytes32 typehash, bytes32 witness, - bytes calldata orderData + bytes calldata context ) external; } diff --git a/src/utility/Utility.sol b/src/utility/Utility.sol new file mode 100644 index 0000000..d0e47e8 --- /dev/null +++ b/src/utility/Utility.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Extsload } from "../lib/Extsload.sol"; +import { ERC6909 } from "solady/tokens/ERC6909.sol"; +import { Tstorish } from "../lib/Tstorish.sol"; + +contract Utility { + address internal constant THE_COMPACT = 0x00000000000000171ede64904551eeDF3C6C9788; + address internal constant TSTORE_TEST_CONTRACT = 0x627c1071d6A691688938Bb856659768398262690; + + bytes4 internal constant EXTTLOAD_SELECTOR = 0xf135baaa; + bytes4 internal constant EXTSLOAD_SELECTOR = 0x1e2eaeaf; + + uint256 internal constant REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268; + // ╭------------------------+---------+------+--------+-------+-------------------------------╮ + // | Name | Type | Slot | Offset | Bytes | Contract | + // +==========================================================================================+ + // | _tstoreSupportActiveAt | uint256 | 0 | 0 | 32 | src/TheCompact.sol:TheCompact | + // ╰------------------------+---------+------+--------+-------+-------------------------------╯ + bytes32 internal constant TSTORE_SUPPORT_ACTIVE_AT_SLOT = 0x00; + + bool internal immutable TSTORE_INITIAL_SUPPORT; + + error TheCompactNotDeployed(); + error BalanceNotSettled(); + + constructor() { + TSTORE_INITIAL_SUPPORT = checkTstoreAvailable(); + if (TSTORE_INITIAL_SUPPORT) { + try Tstorish(THE_COMPACT).__activateTstore() { + // Successfully activated TSTORE + /// @dev This leads to tstore only being active after the current block. + /// As a precaution, we deactivate TSTORE_INITIAL_SUPPORT. + TSTORE_INITIAL_SUPPORT = false; + } catch { + // Failed to activate TSTORE + /// @dev Since we know the chain supports tstore, this call can only revert with: + /// TStoreAlreadyActivated(). We have to read _tstoreSupportActiveAt to confirm it is already active. + bytes32 tstoreSupportActiveAt = Extsload(THE_COMPACT).extsload(TSTORE_SUPPORT_ACTIVE_AT_SLOT); + if (uint256(tstoreSupportActiveAt) > block.number) { + TSTORE_INITIAL_SUPPORT = false; + } + } + } + } + + /// @notice Checks if the Compact is deployed and if transient storage is available on the chain. + function checkTstoreAvailable() internal view returns (bool ok) { + if (TSTORE_TEST_CONTRACT.code.length == 0) { + revert TheCompactNotDeployed(); + } + + // Call the test contract, which will perform a TLOAD test. If the call + // does not revert, then TLOAD/TSTORE is supported. Do not forward all + // available gas, as all forwarded gas will be consumed on revert. + // Note that this assumes that the contract was successfully deployed. + address tstoreTestContract = TSTORE_TEST_CONTRACT; + assembly ("memory-safe") { + ok := staticcall(div(gas(), 10), tstoreTestContract, 0, 0, 0, 0) + } + } + + /// @notice Returns the users balance only if reentrancy protection is not active on the Compact. This eliminates in flight balances before the ERC6909 tokens were burned. + /// @dev The function favors chains supporting eip-1153 (transient storage) + function settledBalanceOf(address owner, uint256 id) internal view returns (uint256 amount) { + bytes32 reentrancySlotContent; + + if (TSTORE_INITIAL_SUPPORT) { + // Only check the tstore reentrancy guard slot + reentrancySlotContent = Extsload(THE_COMPACT).exttload(bytes32(REENTRANCY_GUARD_SLOT)); + } else { + // tstore not initially available. Check if it is active now by reading the tstore support active at slot. + assembly ("memory-safe") { + // Read the tstore support active at slot on the compact + mstore(0x1c, EXTSLOAD_SELECTOR) + mstore(0x20, TSTORE_SUPPORT_ACTIVE_AT_SLOT) + let ok := staticcall(gas(), THE_COMPACT, 0x1c, 0x24, 0x20, 0x20) + if iszero(ok) { + // Indicating the call has failed. Since we ensure the compact is deployed in the constructor, this can only be due to out of gas. + revert(0, 0) + } + + let tstoreSupportActiveAt := mload(0x20) + + // If tstoreSupportActiveAt is 0 or is greater than the current block number, then tstore is not supported in this case. + // 0 could only be indicating tstore is valid, if TSTORE_INITIAL_SUPPORT was true, so we can safely assume tstore is not supported in this case. + let tstoreSupported := and(gt(tstoreSupportActiveAt, 0), iszero(gt(tstoreSupportActiveAt, number()))) + + // If tstore is supported update the selector to read from the transient storage slot + if tstoreSupported { + mstore(0x1c, EXTTLOAD_SELECTOR) + } + + // Update the slot pointer to the reentrancy guard slot + mstore(0x20, REENTRANCY_GUARD_SLOT) + + // Call the Compact to read the reentrancy guard slot + pop(staticcall(gas(), THE_COMPACT, 0x1c, 0x24, 0, 0x20)) + reentrancySlotContent := mload(0) + + // We do not need to check for success. + // If the reentrancy slot read runs out of gas, mload(0) will read the selector, which will trigger a BalanceNotSettled() revert. + } + } + + if (uint256(reentrancySlotContent) > 1) { + revert BalanceNotSettled(); + } + + // If we get here, the balance is settled, so returning the balance + return ERC6909(THE_COMPACT).balanceOf(owner, id); + } +} diff --git a/test/helpers/HelperConstants.sol b/test/helpers/HelperConstants.sol new file mode 100644 index 0000000..7d2c926 --- /dev/null +++ b/test/helpers/HelperConstants.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { TheCompact } from "../../src/TheCompact.sol"; + +library HelperConstants { + /// @dev This deployed bytecode is only for testing purposes and will differ by chain based on constructor logic for actual deployments + bytes internal constant theCompact_deployedBytecode_noTransientStorage = bytes( + hex"60806040526004361015610011575f80fd5b5f3560e01c8062ad800c146136e2578062fdd58e1461368857806301aaa5d81461363b57806301ffc9a7146135c1578063054ab40b1461338c57806306ef7267146132af57806306fdde0314613267578063095bcdb6146131c757806312d4888514613036578063144bd5b514612fbd578063164e131514612e855780631b0196d414612d2c5780631e2eaeaf14612cf25780632a9c4d0d14612a705780632e3ff2f71461294e5780632ea8433c146127e45780632f926732146127a65780633644e5151461276e5780633ddf74001461270e5780633f47e662146126cf578063426a84931461266b578063456b7d0c1461260457806345ebe2181461246657806347c96396146123e15780634a6bf993146120f35780634e41a1fb146120b457806351d35ee114611fc9578063558a729714611f3657806355dc8cfc14611d75578063598af9e714611d0b5780635b15bfd914611c195780636428cb59146119dc5780636d2388651461196557806370030190146118ea5780637423eb3c146118415780637c0ce006146117745780637e2480ab146114e05780638f3994de146114095780638fa31b58146113915780639816962f1461127f578063a38cc1621461121b578063b6363cf2146111b8578063ba377eb414611173578063bf38c9bd1461112b578063c0587a87146110b7578063c42f38e114610d9d578063c87b56dd14610d5e578063cbb6857a14610c13578063d14de84a14610abe578063da2f268b14610a57578063dbd035ff146109c1578063e1950ef51461085f578063e7fd251e146107a9578063e9626d0c1461052c578063eb35a6d21461049b578063f135baaa14610461578063f2d67062146103eb5763fe99049a14610295575f80fd5b60807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576102c7613720565b6102cf613766565b9060443591606435906102e482858386613a76565b67edcaa89a822939406034528260285233601452603460202054156103ae575b83601452604060142080548084116103a157839003905580602852836014526040601420908154928084019384106103945773ffffffffffffffffffffffffffffffffffffffff93849355335f526020521691167f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885960405fa45f603452610389614f5c565b602060405160018152f35b6389560ca15f526004601cfd5b63f4d678b85f526004601cfd5b835f5260545f208054600181016103c7575b5050610304565b8084116103da5783900390555f806103c0565b63deda90305f526004601cfd5b5f80fd5b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757604061041f613720565b61045561042a6137fe565b61043c83610436613789565b92613a32565b9260c4359160a435916084359160643591873491614041565b82519182526020820152f35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576004355c5f5260205ff35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757602060043561052462ffffff77278d00094890015180000f3c00025800003c00000f000001601860078560fc1c16021c164201828161051e8195336014526341d0e04b5f526034526038601c20905f603452565b55614f2b565b604051908152f35b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75761055e613720565b60243567ffffffffffffffff81116103e75761057e90369060040161391a565b91610587613789565b9060a435906105946141d3565b8015330218916105a385613e36565b9380358060601b1560208301353414158116341582141788151761079c576bffffffffffffffffffffffff906b0fffffffffffffffffffffff8360a01c1692816105ec856142fd565b61077f575b509116905b878110610711575050610607614f5c565b604051907ffb7744571d97aa61eb9c2bc3c67b9b1ba047ac9e95afb2ef02bc5b3d9e64fbe58252602082015f906040840190602c85019160608601935b8b81106106e15750505050505060806106c194959660051b9101206040519183835260208301528460408301526064356060830152608435608083015260a082015260c43560c08201527f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b82141560051b60c00190208093614160565b6106dd604051928392835260406020840152604083019061394b565b0390f35b8060019160061b87013580855283525f85528b60208260051b809201015187526080808a20918a01015201610644565b61071c818985613e7b565b90813591836b0fffffffffffffffffffffff8460a01c1603610772576001926020610761920135908973ffffffffffffffffffffffffffffffffffffffff8216614a68565b61076b828a613d05565b52016105f6565b63af3463065f526004601cfd5b61078b903490896140f3565b3461079589613cf8565b52896105f1565b63ca0fc08e5f526004601cfd5b346103e7576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576107e1613720565b6107e9613766565b906044357fffffffffffffffffffffffff0000000000000000000000000000000000000000811681036103e75761084d61083283606095610828613743565b9460643591613de9565b929093610104359160e4359160c43591868860a43593614041565b60405192835260208301526040820152f35b346103e7577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360161010081126103e7576080136103e75761089f613743565b506108a861382d565b506108b16137ac565b60e4359067ffffffffffffffff82116103e7576109b861099d916108db60209436906004016138ec565b916108e46139ee565b6109b33615946108f660a48717614d8e565b9461090084614379565b9b839c9a939991929a60607f63697069656e7429546f6b656e5065726d697373696f6e73286164647265737391607681527f436f6d706163744465706f736974207769746e65737329436f6d70616374446560208201527f706f7369742862797465733132206c6f636b5461672c6164647265737320726560408201527520746f6b656e2c75696e7432353620616d6f756e742960768201520152565b6101008201526101406101e083179217906148ee565b614947565b50610524614f5c565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e757610a109036906004016138bb565b6040519160408360208152836020820152019160051b83019060206040830191935b843554815201928482851015610a4e5750602080910193610a32565b60408185030190f35b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576020600435610a93613766565b906040519183526303f37b1a600c52806040526001603760282054918360405260ff161b1615158152f35b346103e75760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435610af8613766565b60c43567ffffffffffffffff81116103e757602092610b1e6105249236906004016138ec565b93610c0e610bad60a43560a090604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f5e6f7b4e1ac3d625bac418bc955510b3e054cb6cc23cc27885107f080180b29260208301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604083015260608201523060808201522090565b6040517f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b86147f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc787141760051b60a0039081600482372096879484866153ae565b614160565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e75780600401906101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126103e757610d56602092610c946141d3565b60a0610cac610ca660e4860184613fad565b90615443565b9360546050610d11610cbd86615520565b9890879060a07fc3e0b49b35866f940704f2fb568b9d5dae17a245971e2c095778b60ea177f03b9260405194838652336020870152466040870152606086015201356080840152141560051b608001902090565b926040519384525f6004610104830135830101803560051b808c8801928d0183378b01862060808701528986525201602c8301372092610d4f613d19565b9184614e3b565b610524614f5c565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435630e89341c3615176140be565b346103e757610dab3661397e565b610db36141d3565b60e0810190610dc5610ca68383613fad565b610e25610dd183615520565b9290849060a07fc3e0b49b35866f940704f2fb568b9d5dae17a245971e2c095778b60ea177f03b9260405194838652336020870152466040870152606086015201356080840152141560051b608001902090565b6040516101008401358401803560051b9261012086013560051b905f925f5b86811061108857505050501561107b5760a091602001812060808201528281525f60208201526054604c8501602c8301372092610f0561014084013560a090604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f5e6f7b4e1ac3d625bac418bc955510b3e054cb6cc23cc27885107f080180b29260208301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604083015260608201523060808201522090565b92610f0e613d19565b913581019182602001923593841561105357610f2a8585614dc2565b95863592610f7d826b0fffffffffffffffffffffff8660a01c1695610f4e8a613c36565b9a6020610f59613b2d565b9183835201356020820152610f6d8c613cf8565b52610f778b613cf8565b506158ec565b60015b888110610fef5750610fe257610f989488948a614fae565b935f5b8451811015610fd75780610fd1610fb56001938787614dfb565b88610fc36040830183613ca4565b602084013593359291615028565b01610f9b565b602082610524614f5c565b633a03d3bb5f526004601cfd5b80610ffd6001928b8b614dfb565b92602084359161100d88846158ec565b8a6b0fffffffffffffffffffffff8560a01c16141517179461102d613b2d565b92835201356020820152611041828d613d05565b5261104c818c613d05565b5001610f80565b7fcfbccf0c000000000000000000000000000000000000000000000000000000005f5260045ffd5b6371515b9a5f526004601cfd5b80860160208284010135868201528482146110a7575b50602001610e44565b602090810184905294508461109e565b346103e75760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760206111206110f3613720565b6044359060243590605892601c926040519260148401526368a30dd0835260348301526054820152012090565b546040519015158152f35b346103e7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757604063655e83a85463824664ed5482519182526020820152f35b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760206105246111aa6137cf565b6111b2613766565b90613a32565b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576111ef613720565b6111f7613766565b9067edcaa89a822939406020526014525f5260206034600c20546040519015158152f35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e75761127561126f602092369060040161391a565b90614d58565b6040519015158152f35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760206112b86137cf565b6112d26b0fffffffffffffffffffffff8260a01c166142fd565b6112f08133601452632d5c707e5f526034526024601c20905f603452565b9062ffffff77278d00094890015180000f3c00025800003c00000f000001601860078460fc1c16021c16420191805473ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffff0000000000000000000000000000000000000000808660a01b16169116179055815f52337f16c05a1aea0a2659b53f72fda6b47106e4aa07338b16993a01ece024df9d8cc4845fa3604051908152f35b346103e75760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576113c8613720565b6113d06137fe565b60643573ffffffffffffffffffffffffffffffffffffffff811681036103e7576020926114009260443591613de9565b50604051908152f35b346103e7576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435611444613766565b5061144d613789565b60e43567ffffffffffffffff81116103e7576020926114736105249236906004016138ec565b93610c0e61147f613d19565b6040517f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b86147f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc787141760051b60e0039081600482372096879484866153ae565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e757806004016101a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc83360301126103e75761155a6141d3565b611563816154d2565b916115c461157083615520565b9490849060a07fc3e0b49b35866f940704f2fb568b9d5dae17a245971e2c095778b60ea177f03b9260405194838652336020870152466040870152606086015201356080840152141560051b608001902090565b6040516004610144840135840101803560051b9261016485013560051b905f925f5b86811061174557505050501561107b5760a091602001812060808201528481525f6020820152605460508401602c83013720916116a761018483013560a090604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f5e6f7b4e1ac3d625bac418bc955510b3e054cb6cc23cc27885107f080180b29260208301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604083015260608201523060808201522090565b906117136116b3613d19565b9160e485013596846004610104880135976101248101350101946116d5613bd6565b936116de613b2d565b8b81528960208201526116f086613cf8565b526116fa85613cf8565b506b0fffffffffffffffffffffff8b60a01c168a614fae565b918560ff1c15901517156117345793610d5692918560209635908701615028565b8463a06356f55f526020526024601cfd5b8086016020828401013586820152848214611764575b506020016115e6565b602090810184905294508461175b565b346103e75760206117843661397e565b61178c6141d3565b610d56611798826154d2565b9160a06117fb6117a783615520565b9590849060a07fc3e0b49b35866f940704f2fb568b9d5dae17a245971e2c095778b60ea177f03b9260405194838652336020870152466040870152606086015201356080840152141560051b608001902090565b6040519081525f6101408401358401803560051b80898501928a018337880183206080840152868352526054604c8401602c830137209261183a613d19565b918461426b565b346103e7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7575f5415157f0000000000000000000000000000000000000000000000000000000000000000176118dd575f8080807f000000000000000000000000afe8f2e1fb99c727eb85ccf947c8ab0c200205b8600a5a04fa156118d057436001015f55005b6370a4078f5f526004601cfd5b63f45b98b05f526004601cfd5b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043561193a81336014526341d0e04b5f526034526038601c20905f603452565b80541561195057905f6103899255361590614f2b565b5063e632dbad5f52336020526040526044601cfd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757365f803760405f3681347f0000000000000000000000006d93d963d096aa34e6b0919d8c383c993da18d675af1156119d4575f5163655e83a85560205163824664ed55005b3d5f803e3d5ffd5b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e75780600401906101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126103e757611a576141d3565b611a67610ca660e4830184613fad565b9060c4810135810190600482013592602484159301918460a9019260a4605083019201359360405195611ba6577f4261746368436f6d70616374286164647265737320617262697465722c616464865260208601967f726573732073706f6e736f722c75696e74323536206e6f6e63652c75696e743288528060408801937f353620657870697265732c4c6f636b5b5d20636f6d6d69746d656e74732c4d618552606089017f6e64617465206d616e64617465294c6f636b2862797465733132206c6f636b54905260888901674d616e64617465289052608089017f61672c6164647265737320746f6b656e2c75696e7432353620616d6f756e7429905260a8890137860160a801602990538520808652339096525f9052605490604c85013760a083015260c082015260e0902090602092610d56915b610d4f613d19565b5050602095945060c0925090605484927f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b610d56965233888501525f6040850152604c84013760a082015220917f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b611b9e565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435611c656b0fffffffffffffffffffffff8260a01c1661496c565b60078260fc1c168260ff1c9073ffffffffffffffffffffffffffffffffffffffff6040519381861685521660208401526008811015611cde5760408301526002811015611cde5760a0927fffffffffffffffffffffffff0000000000000000000000000000000000000000916060840152166080820152f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b346103e75760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757611d42613720565b611d4a613766565b9067edcaa89a822939406034526028526014526044355f52602060545f20545f603452604051908152f35b60e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757611da7613720565b5060243567ffffffffffffffff81116103e757611dc890369060040161391a565b60607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc3601126103e75760a4359173ffffffffffffffffffffffffffffffffffffffff831683036103e75760c4359067ffffffffffffffff82116103e7576106dd93611f1c611e3e611f229436906004016138ec565b959093611e4c848383614ae2565b9592611f0860c09b9399929b361592611e6760848517614d8e565b611e718d83614c34565b94909460607f63697069656e7429546f6b656e5065726d697373696f6e73286164647265737391607681527f436f6d706163744465706f736974207769746e65737329436f6d70616374446560208201527f706f7369742862797465733132206c6f636b5461672c6164647265737320726560408201527520746f6b656e2c75696e7432353620616d6f756e742960768201520152565b608084015260071b610200019217906148ee565b86614ce3565b60405191829160208352602083019061394b565b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757611f68613720565b602435908115158092036103e75767edcaa89a82293940602052336014525f52806034600c2055602052600c5160601c337fceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267602080a3602060405160018152f35b346103e7576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435612004613766565b5061200d613789565b61201561382d565b5061201e6137ac565b506101243567ffffffffffffffff81116103e7576020926120466105249236906004016138ec565b93610c0e612052613d19565b6040517f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b86147f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc787141760051b610120039081600482372096879484866153ae565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435634e41a1fb3615176140be565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e7578060040160807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc83360301126103e75761216c6141d3565b606482019061217b8282613fad565b939061218685613c36565b945f5b8181106123955750505061219d8383613fad565b6121d981604051917ffb7744571d97aa61eb9c2bc3c67b9b1ba047ac9e95afb2ef02bc5b3d9e64fbe5835260808084019260051b840101604052565b92906020810160608201602c8301604084015f5b878110612352578c8c8c8c60c08d8d60051b9020604051907f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b825233602083015233604083015260446024850135948560608501520135608083015260a0820152209361225a8484613fad565b9081159182610fe25781357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112156103e7576b0fffffffffffffffffffffff90839293013560a01c166122bb6122b48261496c565b809761432d565b6001925b8084106123205750505050610fe257826122dc926122e1966149d5565b613fad565b905f5b8281106122f357610389614f5c565b8061231a6123046001938686614001565b6123116020820182613ca4565b90913591615378565b016122e4565b90919293600190836b0fffffffffffffffffffffff612340888689614001565b3560a01c1614151794019291906122bf565b806123606001928a8a614001565b6123796123738235926020810190613ca4565b9061498f565b9080885284525f85528552608087208160051b8b0152016121ed565b806123a36001928486614001565b6123c06123736123b1613b2d565b92803584526020810190613ca4565b60208201526123cf828a613d05565b526123da8189613d05565b5001612189565b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e75761242b90369060040161391a565b906024359067ffffffffffffffff82116103e75760209261246061245661127594369060040161391a565b9390923391613e8b565b50614d58565b6101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757612499613720565b60243567ffffffffffffffff81116103e7576124b990369060040161391a565b60607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc9392933601126103e75760a43560c4359360038510156103e75760e43567ffffffffffffffff81116103e7576125169036906004016138ec565b93610104359067ffffffffffffffff82116103e757869161253b9036906004016138ec565b919098612549848388614ae2565b959281809e9399925160051b90602001209b6125658a83614c34565b90968236159560018717926125799461441d565b9d604051915f52336020526040528c60605260805f2060808801526040525f60605280151501610153017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169060071b01610180019060c0176125db946148ee565b6125e59489614ce3565b6125ee92614160565b604051809160208252602082016106dd9161394b565b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e75761265161265f91369060040161391a565b612659613766565b91613e8b565b50602060405160018152f35b61267436613866565b909167edcaa89a8229394060345233602852601452815f528060545f20555f5260205160601c337fb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a760205fa45f603452602060405160018152f35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435633f47e6623615176140be565b346103e75760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757602061275e61274a613720565b6127526137fe565b90339160443591613de9565b5061052460843560643533614160565b346103e7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576020610524613d19565b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75761038960243560043533614160565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e757806004019060a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126103e757612940612946926128656141d3565b61286d613bd6565b9061293b612879613b2d565b928260648701359687865260848101956128966123738885613ca4565b60208201526128a484613cf8565b526128ae83613cf8565b506101006128bf6123738885613ca4565b916024604051917f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc783523360208401523360408401520192604c8460608401378a60c08301525f60ac83015260e08201522061293661292e6b0fffffffffffffffffffffff8b60a01c1661496c565b80933561432d565b6149d5565b613ca4565b90615378565b610389614f5c565b346103e75760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435612988613766565b604435906129aa83336014526341d0e04b5f526034526038601c20905f603452565b54428111901517612a5f576129bd6141d3565b33811502188173ffffffffffffffffffffffffffffffffffffffff841680612a065750905f80809338935af15b156129f9576129469133615188565b63580d4e5e5f526004601cfd5b919250612a13308361508d565b926014526034526fa9059cbb0000000000000000000000005f52612a5060205f6044601082865af13d1560015f51141716915f603452309061508d565b908282101691038102906129ea565b82639287bcb05f526020526024601cfd5b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757612aa7613720565b60243567ffffffffffffffff81116103e757612adc73ffffffffffffffffffffffffffffffffffffffff9136906004016138ec565b9190921691823b15158333141790605583149283612c69575b50501715612c5957612be0816b0f0000000000000000000000690fedcba98765432100007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc607f6affffffffffffffffffffff687fffffffffffffffff8660591c1668ffffffffffffffffff8760581c16178060021c178060041c178060081c178060101c178060201c178060401c176755555555555555558160011c169003673333333333333333808260021c169116018060041c01670f0f0f0f0f0f0f0f660f0f0f0f0f0f0f8260081c169116018060101c019516948060201c0116604803161c60581b161790565b8073044036fc77deaed23000000000000000000000001791825480612c4557508060209355815f5282527fc54dcaa67a8fd7b4a9aa6fd57351934c792613d5ec1acbd65274270e6de8f7e460405fa16bffffffffffffffffffffffff60405191168152f35b8263c18b0e975f526020526040526044601cfd5b634e7f492b5f526020526024601cfd5b809192935015612cc5577fff0000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff8282938560405137604051201685149235161416908380612af5565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757600435545f5260205ff35b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757612d636137cf565b612d6b613766565b9073ffffffffffffffffffffffffffffffffffffffff612d9b6b0fffffffffffffffffffffff8360a01c1661496c565b83821691168114612e7857612dc48233601452632d5c707e5f526034526024601c20905f603452565b9081548060a01c42811173ffffffffffffffffffffffffffffffffffffffff8316151516612e68575081612e285750505f90555b337f92de0e90f030663724bafa9b7a9ba2643e3f4ced55f1cfee8b01e2682aeb45fd5f80a4602060405160018152f35b5073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000179055612df8565b63174f07765f526020526024601cfd5b632411f3105f526004601cfd5b346103e7577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360161014081126103e7576080136103e757612ec5613743565b612ecd61382d565b5060c43590600360e43510156103e7576101043567ffffffffffffffff81116103e757612efe9036906004016138ec565b610124359367ffffffffffffffff85116103e75783612fb7612f5b610d5695612f2d60209936906004016138ec565b6109b3612f3b9a929a6139ee565b91612f4583614379565b839f93989299919d361593849260e4359061441d565b9c90604051905f5233602052886040528c60605260805f206101008301526040525f6060526101407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061017f61016084015101169217906148ee565b50614160565b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576040613016612ff9613720565b602435906014526341d0e04b5f526034526038601c20905f603452565b548151908015154282116002030261302d8161385c565b82526020820152f35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e7576130859036906004016138bb565b9061317161316c336b0f0000000000000000000000690fedcba98765432100007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc607f6affffffffffffffffffffff687fffffffffffffffff8660591c1668ffffffffffffffffff8760581c16178060021c178060041c178060081c178060101c178060201c178060401c176755555555555555558160011c169003673333333333333333808260021c169116018060041c01670f0f0f0f0f0f0f0f660f0f0f0f0f0f0f8260081c169116018060101c019516948060201c0116604803161c60581b161790565b6142fd565b809160051b01905b81811061318b57602060405160018152f35b806020913561319a338261432d565b5f52337f378325bc73c84116a385ff3c287be7229f0aa047254a780befebba0fc966216e835fa201613179565b6131d036613866565b90916131de82848333613a76565b67edcaa89a8229394060205233601452825f5260405f2080548084116103a157839003905580601452825f5260405f209182548181019081106103945773ffffffffffffffffffffffffffffffffffffffff9355335f5260205216337f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885960405fa4610389614f5c565b346103e7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e757602080526b0b54686520436f6d70616374604b5260606020f35b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760606133276132eb613720565b6132f36137fe565b9061330e6b0fffffffffffffffffffffff8360a01c166142fd565b601452632d5c707e5f526034526024601c20905f603452565b5473ffffffffffffffffffffffffffffffffffffffff8160a01c911680155f14613369575f915b6040519261335b8161385c565b835260208301526040820152f35b6bffffffffffffffffffffffff82036133845760029161334e565b60019161334e565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043567ffffffffffffffff81116103e7576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126103e7576134026141d3565b60c4810135810160048101359060248215910182608c01906050850160e48601359060f087019260a4880135946040519661353f577f436f6d70616374286164647265737320617262697465722c6164647265737320875260208701977f73706f6e736f722c75696e74323536206e6f6e63652c75696e7432353620657889528060408901937f70697265732c62797465733132206c6f636b5461672c6164647265737320746f8552606b8a016a7465294d616e6461746528905260608a017f6b656e2c75696e7432353620616d6f756e742c4d616e64617465206d616e64619052608b8a01378701608b01602990538620808752339097525f9052605490604c86013760a08401525f60ac84015260349060cc840137610100820152610120902090602092610d56915b613535613d19565b916004018461426b565b50506020969550610100935091603491605486947f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc7610d569852338a8701525f6040870152604c86013760a08401525f60ac84015260cc83013720917f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc761352d565b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576004357fffffffff00000000000000000000000000000000000000000000000000000000811681036103e75760209060e01c604051906301ffc9a7630f632fb3821491141715158152f35b60607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760206136796136726137cf565b3390613a32565b61052460443560243533614160565b346103e75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e7576136bf613720565b67edcaa89a822939406020526014526024355f52602060405f2054604051908152f35b346103e75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103e75760043562ad800c3615176140be565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036103e757565b6084359073ffffffffffffffffffffffffffffffffffffffff821682036103e757565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036103e757565b6044359073ffffffffffffffffffffffffffffffffffffffff821682036103e757565b60c4359073ffffffffffffffffffffffffffffffffffffffff821682036103e757565b600435907fffffffffffffffffffffffff0000000000000000000000000000000000000000821682036103e757565b602435907fffffffffffffffffffffffff0000000000000000000000000000000000000000821682036103e757565b60a435907fffffffffffffffffffffffff0000000000000000000000000000000000000000821682036103e757565b60031115611cde57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60609101126103e75760043573ffffffffffffffffffffffffffffffffffffffff811681036103e757906024359060443590565b9181601f840112156103e75782359167ffffffffffffffff83116103e7576020808501948460051b0101116103e757565b9181601f840112156103e75782359167ffffffffffffffff83116103e757602083818601950101116103e757565b9181601f840112156103e75782359167ffffffffffffffff83116103e7576020808501948460061b0101116103e757565b90602080835192838152019201905f5b8181106139685750505090565b825184526020938401939092019160010161395b565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8201126103e7576004359067ffffffffffffffff82116103e7577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82610160920301126103e75760040190565b60043573ffffffffffffffffffffffffffffffffffffffff811681036103e75790565b3573ffffffffffffffffffffffffffffffffffffffff811681036103e75790565b919091613a4f6b0fffffffffffffffffffffff8260a01c166142fd565b80923415613a6957613a6791349180153302186140f3565b565b63426d8dcf5f526004601cfd5b60a45f918260209473ffffffffffffffffffffffffffffffffffffffff97631a808f9197613aa26141d3565b89613abd6b0fffffffffffffffffffffff8360a01c1661496c565b936040519b8c97878052601c89019c8d99528d8c339101521660408d01521660608b015260808a015260a08901525af17f1a808f91000000000000000000000000000000000000000000000000000000005f5103613b1a57505050565b3d1517156119d45763014c931060a49252fd5b604051906040820182811067ffffffffffffffff821117613b4d57604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f604051930116820182811067ffffffffffffffff821117613b4d57604052565b67ffffffffffffffff8111613b4d5760051b60200190565b604090613be282613b7a565b60018152917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825f5b828110613c1857505050565b6020906040613c2681613b7a565b9036823782828501015201613c0c565b90613c48613c4383613bbe565b613b7a565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0613c768294613bbe565b01905f5b828110613c8657505050565b6020906040613c9481613b7a565b9036823782828501015201613c7a565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156103e7570180359067ffffffffffffffff82116103e757602001918160061b360383136103e757565b805115612cc55760200190565b8051821015612cc55760209160051b010190565b7fc245137e711daa0543be12b489ddf70cec61734e447b142074de49ce72d4fd727f0000000000000000000000000000000000000000000000000000000000007a694618613d645790565b5060a06040517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81527f5e6f7b4e1ac3d625bac418bc955510b3e054cb6cc23cc27885107f080180b29260208201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660408201524660608201523060808201522090565b9392613e2b9291613df986614a4e565b90613e146b0fffffffffffffffffffffff8260a01c166142fd565b17948591613e206141d3565b801533021890614a68565b613e33614f5c565b90565b90613e43613c4383613bbe565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0613e718294613bbe565b0190602036910137565b9190811015612cc55760061b0190565b909291613e966141d3565b8015330218613ea484613e36565b938235908160601b159160208501353414158316341584141782151761079c57806b0fffffffffffffffffffffff849260a01c16613ee1816142fd565b93613f90575b505b818110613efd575050505050613a67614f5c565b613f08818387613e7b565b8035906b0fffffffffffffffffffffff8260a01c166bffffffffffffffffffffffff86168103613f6f575b50600192916020613f5e920135908773ffffffffffffffffffffffffffffffffffffffff8216614a68565b613f68828a613d05565b5201613ee9565b613f5e91955091602083613f85600196956142fd565b969250509192613f33565b613f9c903490866140f3565b34613fa688613cf8565b525f613ee7565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156103e7570180359067ffffffffffffffff82116103e757602001918160051b360383136103e757565b9190811015612cc55760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1813603018212156103e7570190565b94613a6796959897939498801533021898604051958787526020870152896040870152606086015260808501528060a085015260c08401525f60ac84015260e08301526101008201527f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc782141560051b6101000190208094614160565b6024601c5f939284933060145261d694855260026034536017601e209185526020525afa3d5f803e156140ef573d5ff35b3d5ffd5b91909167edcaa89a8229394060205280601452825f5260405f209182548181019081106103945773ffffffffffffffffffffffffffffffffffffffff9355335f52602052165f7f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859604082a4565b909173ffffffffffffffffffffffffffffffffffffffff9260016141a5838386605892601c926040519260148401526368a30dd0835260348301526054820152012090565b555f52602052167f52dd3aeaf9d70bfcfdd63526e155ba1eea436e7851acf5c950299321c671b92760405fa2565b7f00000000000000000000000000000000000000000000000000000000000000001568929eee149b4bd212685c8161424d575b6001811161423d5750614222575b3368929eee149b4bd212685d565b5f5443811190151715614214573368929eee149b4bd2126855565b63f57c448b5f526020526024601cfd5b5f5443811190151715614206575068929eee149b4bd2126854614206565b610120820135820194936101008301359360e0840135936142cf93925f91614291613bd6565b9461429a613b2d565b8881528960208201526142ac87613cf8565b526142b686613cf8565b506b0fffffffffffffffffffffff8860a01c1690614fae565b8160ff1c15600117156142ec5784613a6794953590602001615028565b5063a06356f55f526020526024601cfd5b8073044036fc77deaed230000000000000000000000017541561431d5750565b63cf90c3a85f526020526024601cfd5b604051916020526303f37b1a600c5280604052603760282090600182549160ff161b9080821661435f57179055604052565b6fdbc205b1000000000000000000000000600c526044601cfd5b906143826141d3565b6143d760a4357fffffffffffffffffffffffff00000000000000000000000000000000000000006143b285614a4e565b916143cd6b0fffffffffffffffffffffff8260a01c166142fd565b161792309061508d565b906040519065ffffffffffff60405263137c29fe82526080600460208401373060a083015260243560c083015260843560e0830152610140610120830152610160820190565b94935f9384939092918490156020890181614899575b8615614830575b505f93801561476b575b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810115614669575b841561465c5785156144d7575050509181818694600196943701956e75696e7432353620616d6f756e7429600f8801527f29546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c8752600f81880301905282818703019020940301902090565b9198945094507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff692955083915060051b946e75696e7432353620616d6f756e7429838201527f29546f6b656e5065726d697373696f6e73286164647265737320746f6b656e2c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe78201520301905261460c575b82156145bc575b7f73b631296de001508966ddfc334593ad8f850ccd3be4d2c58a9ed469844eebc75f527f179fcd593ea3b4b32623a455fb55eb007c5040f4c85774f2e3f18d98e87eb76b6020525190565b7f25686dcdaf36339365d8aad4b420a3460867a181238971ffae587b16c6d9660f5f527fa794ed1a28cdf297ac45a3eee4643e35d29b295a389368da5f6baa420872c9b760205280519250614571565b7f8b05b54b25c4a22095273abeb15e89077542cdca8be672282102c3473780942c5f527f5a6488a03f679efdf6390ea1cada208092f98514652ffa4036265fd48bcdbf4f6020528051925061456a565b63dae3f1085f526004601cfd5b9693507f4261746368436f6d7061637420636f6d70616374294261746368436f6d70616384527f74286164647265737320617262697465722c616464726573732073706f6e736f60208501526f2c4d616e64617465206d616e646174656062850152714c6f636b5b5d20636f6d6d69746d656e747360528501527f722c75696e74323536206e6f6e63652c75696e7432353620657870697265732c6040850152674d616e6461746528609d8501527f294c6f636b2862797465733132206c6f636b5461672c6164647265737320746f607286151560041b8601726b656e2c75696e7432353620616d6f756e742960858201520152601560bd850194019661446d565b7f436f6d7061637420636f6d7061637429436f6d7061637428616464726573732088527f617262697465722c616464726573732073706f6e736f722c75696e743235362060208901527f6e6f6e63652c75696e7432353620657870697265732c62797465733132206c6f60408901527a6e742c4d616e64617465206d616e64617465294d616e6461746528607b8901527f636b5461672c6164647265737320746f6b656e2c75696e7432353620616d6f7560608901526010880197609b019450614444565b7f426174636841637469766174696f6e207769746e657373294261746368416374905267365b5d206964732c60488a01527f69766174696f6e286164647265737320616374697661746f722c75696e74323560408a01526068890196506038890195505f61443a565b7a647265737320616374697661746f722c75696e743235362069642c603b8b01527f41637469766174696f6e207769746e6573732941637469766174696f6e2861648152605b8a01975060338a019650614433565b935f9281849395826148fe6150bb565b978901526040838901836020820152013701602401601c8501826e22d473030f116ddee9f6b43ac78ba35af1161561493557604052565b3d6119d457637f28c61e5f526004601cfd5b6149569094939294309061508d565b9182811015613a6957613a6792039384916140f3565b908173044036fc77deaed2300000000000000000000000175491821561431d5750565b5f92918391602081019160061b015b8082106149bf5750506149ad57565b634e487b715f5260116020526024601cfd5b909160409083359586019586101792019061499e565b60408301356020840135936149e9826151f9565b8035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1813603018212156103e7570180359067ffffffffffffffff82116103e75760200181360381136103e757613a6796614a489387338789615214565b3361532e565b8060601b15614a5a5790565b63961c9a4f6040526024605cfd5b9190614a74308461508d565b9260405194606052306040523360601b602c526f23b872dd000000000000000000000000600c5260205f6064601c82855af18060015f51141615614ac4575b50613e33945f606052604052614947565b3d823b15171015614ad5575f614ab3565b637939f4245f526004601cfd5b9192614aec6141d3565b823560601b159260208101353414158416341585141783151761079c57839260843595614b4c6b0fffffffffffffffffffffff7fffffffffffffffffffffffff000000000000000000000000000000000000000089169860a01c166142fd565b614b5582613e36565b94614c17575b5084810395614b6987613e36565b925f925f5b898110614b8d5750505050614b805790565b630f2f1e515f526004601cfd5b80614c038a600193979596970194614bae614ba987878a613e7b565b613a11565b9073ffffffffffffffffffffffffffffffffffffffff82167fffffffffffffffffffffffff00000000000000000000000000000000000000008a1617988911151797614bfb81978d613d05565b52309061508d565b614c0d8289613d05565b5201929192614b6e565b614c23908734916140f3565b85614c2d85613cf8565b525f614b5b565b91906040519265ffffffffffff604052610180808260061b8060448560071b9660061b6024350163fe8ec1a78a5260c060208b0152826101400160408b015260043560608b0152876101600160a08b0152606060e08b015281356101008b01526064356101208b0152866101408b015282602482016101608c0137828a0196610160880152016101a086013783010191015b818110614cd857505082016101800190565b308152604001614cc6565b83515f96909594939290875b878110614d0a575050505050505050613a6957613a67614f5c565b80614d5283600193019a614d2c8c614d27614ba930928c8b613e7b565b61508d565b90614d48614d3a858c613d05565b519183831015179d89613d05565b519103908a6140f3565b01614cef565b905f5b818110614d6a57505050600190565b80614d88614d7b6001938587613e7b565b6020810135903533614160565b01614d5b565b60609060408051917faced9f7c53bfda31d043cbef88f9ee23b8171ec904889af3d5d0b9b81914a404835260208301372090565b9015612cc5578035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1813603018212156103e7570190565b9190811015612cc55760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1813603018212156103e7570190565b929060e081013581019182602001923593841561105357614e5c8585614dc2565b95863592614e805f6b0fffffffffffffffffffffff8660a01c1695610f4e8a613c36565b60015b888110614ec75750610fe257614e9c9488945f93614fae565b935f5b8451811015614ebf5780614eb9610fb56001938787614dfb565b01614e9f565b509350505050565b80614ed56001928b8b614dfb565b926020843591614ee55f846158ec565b8a6b0fffffffffffffffffffffff8560a01c161415171794614f05613b2d565b92835201356020820152614f19828d613d05565b52614f24818c613d05565b5001614e83565b908015155f52602052337fe27f5e0382cf5347965fc81d5c81cd141897fe9ce402d22c496b7c2ddc84e5fd60405fa3565b7f000000000000000000000000000000000000000000000000000000000000000015614f92575b600168929eee149b4bd212685d565b5f5443811190151715614f8357600168929eee149b4bd2126855565b919561502291613a6796989795949561500e604c82013560601c9a848c60608501359a8b9a614ff4614fed608089013599614fe88b6151f9565b61496c565b809d61432d565b60208701358701918115020191813591602001908b615902565b8035019182359260200191878b8789615214565b8561532e565b5f949385939192845b81811061505c57505050505082821017615049575050565b633078b2f65f526020526040526044601cfd5b8061508761507060019385899c9a9c613e7b565b9760208901358091019a818c1017983586886156a3565b01615031565b602460106020939284936014526f70a082310000000000000000000000005f525afa601f3d11166020510290565b7f00000000000000000000000000000000000000000000000000000000000000016150f6576e22d473030f116ddee9f6b43ac78ba33b151590565b600190565b91929067edcaa89a8229394060205282601452835f5260405f2080548084118415176103a157839003905580601452835f5260405f20908154928084019384106103945773ffffffffffffffffffffffffffffffffffffffff93849355335f526020521691167f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac72885960405fa4565b909167edcaa89a8229394060205281601452825f5260405f20918254918281118115176103a1575f938173ffffffffffffffffffffffffffffffffffffffff94039055338452602052167f1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859604083a4565b428111156152045750565b63f80dbaea5f526020526024601cfd5b96949190939592956040519673ffffffffffffffffffffffffffffffffffffffff8451938460061b97637bb023f78b5260208b01523360408b0152166060890152608088015260a087015260e060c0870152610100840160e08701528061010087015260051b905f5b8281106153085750505082825f94601c936101408896602099018361012082015201378480520161012401910182855af17f7bb023f7000000000000000000000000000000000000000000000000000000005f51036152da575050565b3d1517156119d457632ce89d2a5f5273ffffffffffffffffffffffffffffffffffffffff166020526024601cfd5b8060209160011b880161014083808487010151805161012085015201519101520161527d565b9192905f5260205273ffffffffffffffffffffffffffffffffffffffff8033931691167f9a071f16ca19062495c8c0e832e4541b4453cd2995fd631b0b7e9f0ee300ff1260405fa4565b91905f5b8181106153895750505050565b806153a861539a6001938588613e7b565b6020810135903586336156a3565b0161537c565b604080516119015f526020969096529081526042601e209490529192906153d78482848661582d565b61543d577f1626ba7e000000000000000000000000000000000000000000000000000000009260209260405190858252600482015260648660248301978895604087528160448601528385013701915afa91511416613a6757638baa579f5f526004601cfd5b50505050565b9061548081604051917ffb7744571d97aa61eb9c2bc3c67b9b1ba047ac9e95afb2ef02bc5b3d9e64fbe5835260808084019260051b840101604052565b9290602c8101604c8201602083015f5b8681106154a55750505050505060051b902090565b806034600c6154b76001948b8b614dfb565b803586520185375f8552608086208160051b8a015201615490565b608090603460ec604051927ffb7744571d97aa61eb9c2bc3c67b9b1ba047ac9e95afb2ef02bc5b3d9e64fbe5845260e081013560208501525f602c85015201604c830137205f5260205f2090565b906040519160c0810135019182359283156156595783907f4d756c7469636861696e436f6d7061637428616464726573732073706f6e736f83527f722c75696e74323536206e6f6e63652c75696e7432353620657870697265732c60208401527f456c656d656e745b5d20656c656d656e747329456c656d656e7428616464726560408401527f737320617262697465722c75696e7432353620636861696e49642c4c6f636b5b60608401527f5d20636f6d6d69746d656e74732c4d616e64617465206d616e64617465294c6f6080840152772c75696e7432353620616d6f756e74294d616e646174652860b88401527f636b2862797465733132206c6f636b5461672c6164647265737320746f6b656e60a084015260200160d8830137602960d884830101538260860160538201209260d901902090565b505090507fc3e0b49b35866f940704f2fb568b9d5dae17a245971e2c095778b60ea177f03b907f172d857ea70e48d30dcad00bb0fc789a34f09c5545da1245400da01d4ef6c8a290565b9273ffffffffffffffffffffffffffffffffffffffff8316927fffffffffffffffffffffffff000000000000000000000000000000000000000016806157b0575091829073ffffffffffffffffffffffffffffffffffffffff8316801590811561575357505f38818086865a60011cf15b1561572557505050613a6792615188565b909193505a9063e718e7450263824664ed18541161574657613a67936150fb565b63c52745985f526004601cfd5b945061575f308661508d565b9482601452836034526fa9059cbb0000000000000000000000005f526157a160205f6044601082865a60011cf13d1560015f51141716915f603452309061508d565b90868210169503850294615714565b9092907fffffffffffffffffffffffff0000000000000000000000000000000000000000831681036157e65750613a67936150fb565b83836158289273ffffffffffffffffffffffffffffffffffffffff613a6798961617946158236b0fffffffffffffffffffffff8760a01c166142fd565b615188565b6140f3565b90939291935f9473ffffffffffffffffffffffffffffffffffffffff8316338114809115176158e4575060405193806040146158a0576041146158705750505050565b60408092939496508101355f1a60205281375b5f526020600160805f825afa511860601b3d11915f606052604052565b507f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9192939550602081013590601b8260ff1c016020523560405216606052615883565b955050505050565b60ff1c6002811015611cde576001149015151690565b9195615932909491948386605892601c926040519260148401526368a30dd0835260348301526054820152012090565b80549081615b58575b50615b5057604080516119015f526020929092528281526042601e20919052916159678187858761582d565b615b50576040517f1626ba7e0000000000000000000000000000000000000000000000000000000081528360048201527f1626ba7e00000000000000000000000000000000000000000000000000000000602060248301809360408252856044820152858b60648301376064860190895a60011cfa91511416615b50577fffffffffffffffffffffffff0000000000000000000000000000000000000000615a0e86613cf8565b515116945f9080516001915b818310615b0f57505050615b025773ffffffffffffffffffffffffffffffffffffffff615a5b8686601452632d5c707e5f526034526024601c20905f603452565b5416928315615af5576020965f96601c948493604051988b8a015289600c8a015263f699ba1c89526040890152606088015260a0608088015260a08701528660ac8701528160c087015260e086013784805260c4019201905afa7ff699ba1c000000000000000000000000000000000000000000000000000000005f5103615ae05750565b3d1517156119d457638baa579f5f526004601cfd5b638baa579f5f526004601cfd5b63bbfc3c515f526004601cfd5b909192600190897fffffffffffffffffffffffff0000000000000000000000000000000000000000615b418786613d05565b51511614151793019190615a1a565b505050505050565b5f90555f61593b56fea164736f6c634300081e000a" + ); +} diff --git a/test/utility/Utility.t.sol b/test/utility/Utility.t.sol new file mode 100644 index 0000000..0b53d5c --- /dev/null +++ b/test/utility/Utility.t.sol @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Utility } from "../../src/utility/Utility.sol"; +import { TheCompact } from "../../src/TheCompact.sol"; +import { ERC20 } from "solady/tokens/ERC20.sol"; + +import { Setup } from "../integration/Setup.sol"; +import { HelperConstants } from "../helpers/HelperConstants.sol"; +import { Claim } from "../../src/types/Claims.sol"; +import { Component } from "../../src/types/Components.sol"; + +// ============= Test Harness ============= + +contract UtilityTestHarness is Utility { + function THE_COMPACT_ADDRESS() external pure returns (address) { + return THE_COMPACT; + } + + function TSTORE_TEST_CONTRACT_ADDRESS() external pure returns (address) { + return TSTORE_TEST_CONTRACT; + } + + function exposed_checkTstoreAvailable() external view returns (bool) { + return checkTstoreAvailable(); + } + + function exposed_settledBalanceOf(address owner, uint256 id) external view returns (uint256) { + return settledBalanceOf(owner, id); + } + + function tstoreInitialSupport() external view returns (bool) { + return TSTORE_INITIAL_SUPPORT; + } +} + +// ============= Test Contracts ============= + +contract UtilityTest is Setup { + UtilityTestHarness public utilityHarness; + uint256 private id; + + function setUp() public override { + super.setUp(); + + if (vm.envOr("COVERAGE", false)) { + // Deploy the compact on the correct address for coverage + vm.etch(address(0x00000000000000171ede64904551eeDF3C6C9788), address(theCompact).code); + theCompact = TheCompact(address(0x00000000000000171ede64904551eeDF3C6C9788)); + // TSTORE_TEST_CONTRACT + vm.etch(address(0x627c1071d6A691688938Bb856659768398262690), hex"3d5c"); + } + + // Deploy harness AFTER environment is ready + utilityHarness = new UtilityTestHarness(); + } + + function test_checkTheCompactAddress() public view { + assertEq(address(theCompact), utilityHarness.THE_COMPACT_ADDRESS()); + } + + function test_checkCheckTstoreAvailable_success() public view { + if (!vm.envOr("COVERAGE", false)) { + bool available = utilityHarness.exposed_checkTstoreAvailable(); + assertTrue(available); + } + } + + function test_checkCheckTstoreAvailable_failure() public { + // Deploy a contract that immediately reverts to simulate TSTORE not being available + bytes memory revertCode = hex"5f5ffd"; // PUSH0 PUSH0 REVERT + vm.etch(utilityHarness.TSTORE_TEST_CONTRACT_ADDRESS(), revertCode); + bool available = utilityHarness.exposed_checkTstoreAvailable(); + assertFalse(available); + } +} + +contract UtilityTest_Transient is Setup { + UtilityTestHarness public utilityHarness; + bytes12 lockTag; + uint256 private idEth; + uint256 private idERC20; + CheckBalanceDuringTransfer public checkBalanceDuringTransfer; + + function setUp() public override { + super.setUp(); + + if (vm.envOr("COVERAGE", false)) { + // Deploy the compact on the correct address for coverage + vm.etch(address(0x00000000000000171ede64904551eeDF3C6C9788), address(theCompact).code); + theCompact = TheCompact(address(0x00000000000000171ede64904551eeDF3C6C9788)); + // TSTORE_TEST_CONTRACT + vm.etch(address(0x627c1071d6A691688938Bb856659768398262690), hex"3d5c"); + } + + // Deploy harness AFTER environment is ready (tstore available) + utilityHarness = new UtilityTestHarness(); + + (, lockTag) = _registerAllocator(alwaysOKAllocator); + idEth = theCompact.depositNative{ value: 1e18 }(lockTag, address(this)); + + // Deploy malicious ERC20 token with harness + checkBalanceDuringTransfer = new CheckBalanceDuringTransfer(utilityHarness); + // Set approval + checkBalanceDuringTransfer.approve(address(theCompact), 1e18); + // Deposit malicious ERC20 token + idERC20 = theCompact.depositERC20(address(checkBalanceDuringTransfer), lockTag, 1e18, address(this)); + checkBalanceDuringTransfer.setId(idERC20); + } + + function test_makeSureTransientStorageIsUsed() public { + vm.expectRevert(abi.encodeWithSignature("TStoreAlreadyActivated()")); + theCompact.__activateTstore(); + } + + function test_checkSettledBalanceOf_transient() public view { + uint256 balance = utilityHarness.exposed_settledBalanceOf(address(this), idEth); + assertEq(balance, 1e18); + } + + function test_checkSettledBalanceOf_transient_reentrant() public { + uint256 balance = utilityHarness.exposed_settledBalanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 0); + + // Check successful withdrawal without reentrancy + Component memory component = + Component({ claimant: uint256(bytes32(abi.encodePacked(bytes12(0), address(this)))), amount: 1e18 }); + Component[] memory claimants = new Component[](1); + claimants[0] = component; + Claim memory claim = Claim({ + allocatorData: bytes(""), + sponsorSignature: bytes(""), + sponsor: address(this), + nonce: 0, + expires: type(uint32).max, + witness: bytes32(0), + witnessTypestring: "", + id: idERC20, + allocatedAmount: 1e18, + claimants: claimants + }); + theCompact.claim(claim); + + balance = utilityHarness.exposed_settledBalanceOf(address(this), idERC20); + assertEq(balance, 0); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 0); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 1e18); + + checkBalanceDuringTransfer.approve(address(theCompact), 1e18); + + // Activate reentrancy balance to check the deposit revert + checkBalanceDuringTransfer.setAfterTokenTransferActive(true); + vm.expectRevert(abi.encodeWithSignature("TransferFromFailed()")); + theCompact.depositERC20(address(checkBalanceDuringTransfer), lockTag, 1e18, address(this)); + // Deactivate reentrancy balance to deposit correctly + checkBalanceDuringTransfer.setAfterTokenTransferActive(false); + + // Deposit again + theCompact.depositERC20(address(checkBalanceDuringTransfer), lockTag, 1e18, address(this)); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 0); + + // Activate reentrancy balance + checkBalanceDuringTransfer.setAfterTokenTransferActive(true); + + // Try to claim again - should fail due to an invalid balance check in the _afterTokenTransfer hook + claim.nonce++; + // The claim transaction will NOT FAIL, even if the claim transfer actually failed. Instead it will release the tokens, which will trigger a release (no balance change) + theCompact.claim(claim); + + // While the claim only silently failed the balance should still NOT have been affected + balance = utilityHarness.exposed_settledBalanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 0); + } +} + +contract UtilityTest_NonTransient is Setup { + UtilityTestHarness public utilityHarness; + bytes12 lockTag; + uint256 private idEth; + uint256 private idERC20; + CheckBalanceDuringTransfer public checkBalanceDuringTransfer; + + function setUp() public override { + super.setUp(); + + if (vm.envOr("COVERAGE", false)) { + // Deploy the compact on the correct address for coverage + vm.etch(address(0x00000000000000171ede64904551eeDF3C6C9788), address(theCompact).code); + theCompact = TheCompact(address(0x00000000000000171ede64904551eeDF3C6C9788)); + } + + // CRITICAL: Make TSTORE_TEST_CONTRACT revert to simulate tstore unavailable BEFORE deploying harness + bytes memory revertCode = hex"5f5ffd"; // PUSH0 PUSH0 REVERT + vm.etch(address(0x627c1071d6A691688938Bb856659768398262690), revertCode); + + // Deploy harness AFTER environment manipulation (tstore unavailable) + utilityHarness = new UtilityTestHarness(); + + // Also etch the no-tstore bytecode to TheCompact for the actual tests + bytes memory deployedCode = HelperConstants.theCompact_deployedBytecode_noTransientStorage; + vm.etch(address(0x00000000000000171ede64904551eeDF3C6C9788), deployedCode); + + (, lockTag) = _registerAllocator(alwaysOKAllocator); + idEth = theCompact.depositNative{ value: 1e18 }(lockTag, address(this)); + + // Deploy malicious ERC20 token with harness + checkBalanceDuringTransfer = new CheckBalanceDuringTransfer(utilityHarness); + // Set approval + checkBalanceDuringTransfer.approve(address(theCompact), 1e18); + // Deposit malicious ERC20 token + idERC20 = theCompact.depositERC20(address(checkBalanceDuringTransfer), lockTag, 1e18, address(this)); + checkBalanceDuringTransfer.setId(idERC20); + } + + function test_checkTheCompactAddress() public view { + assertEq(address(theCompact), utilityHarness.THE_COMPACT_ADDRESS()); + } + + function test_makeSureTransientStorageIsNotUsed() public { + theCompact.__activateTstore(); + } + + /// @notice Test that when Utility successfully activates tstore on TheCompact, + /// TSTORE_INITIAL_SUPPORT is false (activation pending next block) + function test_constructor_setsFalse_whenSuccessfullyActivatesTstore() public { + // Confirm _tstoreSupportActiveAt is 0 before deployment + bytes32 activeAtBefore = theCompact.extsload(bytes32(uint256(0))); + assertEq(uint256(activeAtBefore), 0, "_tstoreSupportActiveAt should be 0 before activation"); + + // Re-enable tstore on the chain + vm.etch(address(0x627c1071d6A691688938Bb856659768398262690), hex"3d5c"); + + // Deploy a new Utility - it should successfully call __activateTstore() + // because TheCompact has _tstoreInitialSupport = false and _tstoreSupportActiveAt = 0 + UtilityTestHarness newHarness = new UtilityTestHarness(); + + // TSTORE_INITIAL_SUPPORT should be false because activation is pending (next block) + assertFalse( + newHarness.tstoreInitialSupport(), "TSTORE_INITIAL_SUPPORT should be false after successful activation" + ); + + // Confirm _tstoreSupportActiveAt is now block.number + 1 + bytes32 activeAtAfter = theCompact.extsload(bytes32(uint256(0))); + assertEq(uint256(activeAtAfter), block.number + 1, "_tstoreSupportActiveAt should be block.number + 1"); + } + + /// @notice Test that settledBalanceOf uses extsload when _tstoreSupportActiveAt = 0 + /// This is the default state when tstore was never activated + function test_settledBalanceOf_usesExtsload_whenActivationIsZero() public { + // Ensure _tstoreSupportActiveAt is 0 + vm.store(address(theCompact), bytes32(uint256(0)), bytes32(uint256(0))); + + // Set persistent storage reentrancy slot to value > 1 to trigger revert if read + bytes32 reentrancySlot = bytes32(uint256(0x929eee149b4bd21268)); + vm.store(address(theCompact), reentrancySlot, bytes32(uint256(2))); + + // settledBalanceOf should read from persistent storage (extsload) and revert + vm.expectRevert(abi.encodeWithSignature("BalanceNotSettled()")); + utilityHarness.exposed_settledBalanceOf(address(this), idEth); + } + + /// @notice Test that settledBalanceOf uses extsload when _tstoreSupportActiveAt > block.number + /// This happens when tstore activation is pending (takes effect next block) + function test_settledBalanceOf_usesExtsload_whenActivationPending() public { + // Set _tstoreSupportActiveAt to a future block + vm.store(address(theCompact), bytes32(uint256(0)), bytes32(block.number + 1)); + + // Set persistent storage reentrancy slot to value > 1 to trigger revert if read + bytes32 reentrancySlot = bytes32(uint256(0x929eee149b4bd21268)); + vm.store(address(theCompact), reentrancySlot, bytes32(uint256(2))); + + // settledBalanceOf should read from persistent storage (extsload) and revert + vm.expectRevert(abi.encodeWithSignature("BalanceNotSettled()")); + utilityHarness.exposed_settledBalanceOf(address(this), idEth); + } + + /// @notice Test that settledBalanceOf uses exttload when _tstoreSupportActiveAt <= block.number + /// This happens when tstore has been activated and is now active + function test_settledBalanceOf_usesExttload_whenTstoreActive() public { + // Set _tstoreSupportActiveAt to current block (active now) + vm.store(address(theCompact), bytes32(uint256(0)), bytes32(block.number)); + + // Set persistent storage reentrancy slot to value > 1 + // If extsload is used, this would cause a revert + bytes32 reentrancySlot = bytes32(uint256(0x929eee149b4bd21268)); + vm.store(address(theCompact), reentrancySlot, bytes32(uint256(2))); + + // settledBalanceOf should read from transient storage (exttload), which is 0 + // So it should NOT revert and return the balance + uint256 balance = utilityHarness.exposed_settledBalanceOf(address(this), idEth); + assertEq(balance, 1e18, "Should return correct balance when reading from transient storage"); + } + + function test_checkSettledBalanceOf_nonTransient() public view { + uint256 balance = utilityHarness.exposed_settledBalanceOf(address(this), idEth); + assertEq(balance, 1e18); + } + + function test_checkSettledBalanceOf_nonTransient_reentrant() public { + uint256 balance = utilityHarness.exposed_settledBalanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 0); + + // Check successful withdrawal without reentrancy + Component memory component = + Component({ claimant: uint256(bytes32(abi.encodePacked(bytes12(0), address(this)))), amount: 1e18 }); + Component[] memory claimants = new Component[](1); + claimants[0] = component; + Claim memory claim = Claim({ + allocatorData: bytes(""), + sponsorSignature: bytes(""), + sponsor: address(this), + nonce: 0, + expires: type(uint32).max, + witness: bytes32(0), + witnessTypestring: "", + id: idERC20, + allocatedAmount: 1e18, + claimants: claimants + }); + theCompact.claim(claim); + + balance = utilityHarness.exposed_settledBalanceOf(address(this), idERC20); + assertEq(balance, 0); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 0); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 1e18); + + checkBalanceDuringTransfer.approve(address(theCompact), 1e18); + + // Activate reentrancy balance to check the deposit revert + checkBalanceDuringTransfer.setAfterTokenTransferActive(true); + vm.expectRevert(abi.encodeWithSignature("TransferFromFailed()")); + theCompact.depositERC20(address(checkBalanceDuringTransfer), lockTag, 1e18, address(this)); + // Deactivate reentrancy balance to deposit correctly + checkBalanceDuringTransfer.setAfterTokenTransferActive(false); + + // Deposit again + theCompact.depositERC20(address(checkBalanceDuringTransfer), lockTag, 1e18, address(this)); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 0); + + // Activate reentrancy balance + checkBalanceDuringTransfer.setAfterTokenTransferActive(true); + + // Try to claim again - should fail due to an invalid balance check in the _afterTokenTransfer hook + claim.nonce++; + // The claim transaction will NOT FAIL, even if the claim transfer actually failed. Instead it will release the tokens, which will trigger a release (no balance change) + theCompact.claim(claim); + + // While the claim only silently failed the balance should still NOT have been affected + balance = utilityHarness.exposed_settledBalanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = theCompact.balanceOf(address(this), idERC20); + assertEq(balance, 1e18); + balance = checkBalanceDuringTransfer.balanceOf(address(this)); + assertEq(balance, 0); + } +} + +// ============= Constructor Activation Tests ============= + +contract UtilityTest_ConstructorActivation is Setup { + function setUp() public override { + super.setUp(); + + if (vm.envOr("COVERAGE", false)) { + // Deploy the compact on the correct address for coverage + vm.etch(address(0x00000000000000171ede64904551eeDF3C6C9788), address(theCompact).code); + theCompact = TheCompact(address(0x00000000000000171ede64904551eeDF3C6C9788)); + // TSTORE_TEST_CONTRACT + vm.etch(address(0x627c1071d6A691688938Bb856659768398262690), hex"3d5c"); + } + } + + /// @notice Test that when Utility is deployed after TheCompact has already activated tstore, + /// TSTORE_INITIAL_SUPPORT is true (the default Setup scenario) + function test_constructor_setsTrue_whenTstoreAlreadyActiveFromSetup() public { + // In the Setup, TheCompact is deployed with tstore support and it's already activated. + // When Utility constructor tries __activateTstore(), it reverts with TStoreAlreadyActivated. + // The constructor then reads _tstoreSupportActiveAt which is <= block.number, + // so TSTORE_INITIAL_SUPPORT remains true. + UtilityTestHarness harness = new UtilityTestHarness(); + + // TSTORE_INITIAL_SUPPORT should be true because tstore is already active + assertTrue( + harness.tstoreInitialSupport(), "TSTORE_INITIAL_SUPPORT should be true when tstore is already active" + ); + } + + /// @notice Test that when tstore is available but activation is pending (future block), + /// TSTORE_INITIAL_SUPPORT is false because _tstoreSupportActiveAt > block.number + function test_constructor_setsFalse_whenActivationPending() public { + // Set _tstoreSupportActiveAt to a future block to simulate pending activation + uint256 futureBlock = block.number + 1; + vm.store(address(theCompact), bytes32(uint256(0)), bytes32(futureBlock)); + + // Deploy Utility - it should try to activate tstore, get TStoreAlreadyActivated, + // then read _tstoreSupportActiveAt which is in the future, so TSTORE_INITIAL_SUPPORT = false + UtilityTestHarness harness = new UtilityTestHarness(); + + // TSTORE_INITIAL_SUPPORT should be false because activation is pending + assertFalse( + harness.tstoreInitialSupport(), "TSTORE_INITIAL_SUPPORT should be false when tstore activation is pending" + ); + } + + /// @notice Test that when tstore is available and already activated (in a previous block), + /// TSTORE_INITIAL_SUPPORT is true because _tstoreSupportActiveAt <= block.number + function test_constructor_setsTrue_whenAlreadyActivatedPreviousBlock() public { + // Set _tstoreSupportActiveAt to a block in the past + uint256 pastBlock = block.number - 1; + vm.store(address(theCompact), bytes32(uint256(0)), bytes32(pastBlock)); + + // Deploy Utility - it should try to activate tstore, get TStoreAlreadyActivated, + // then read _tstoreSupportActiveAt which is in the past, so TSTORE_INITIAL_SUPPORT stays true + UtilityTestHarness harness = new UtilityTestHarness(); + + // TSTORE_INITIAL_SUPPORT should be true because tstore is already active + assertTrue( + harness.tstoreInitialSupport(), "TSTORE_INITIAL_SUPPORT should be true when tstore is already active" + ); + } + + /// @notice Test that when tstore is available and already activated at exactly block.number, + /// TSTORE_INITIAL_SUPPORT is true because _tstoreSupportActiveAt <= block.number + function test_constructor_setsTrue_whenActivatedAtCurrentBlock() public { + // Set _tstoreSupportActiveAt to exactly the current block + vm.store(address(theCompact), bytes32(uint256(0)), bytes32(block.number)); + + // Deploy Utility + UtilityTestHarness harness = new UtilityTestHarness(); + + // TSTORE_INITIAL_SUPPORT should be true because _tstoreSupportActiveAt <= block.number + assertTrue( + harness.tstoreInitialSupport(), + "TSTORE_INITIAL_SUPPORT should be true when tstore activated at current block" + ); + } + + /// @notice Test that when tstore is NOT available on the chain, + /// TSTORE_INITIAL_SUPPORT is false and no activation is attempted + function test_constructor_noActivation_whenTstoreUnavailable() public { + // Make TSTORE_TEST_CONTRACT revert to simulate tstore unavailable + bytes memory revertCode = hex"5f5ffd"; // PUSH0 PUSH0 REVERT + vm.etch(address(0x627c1071d6A691688938Bb856659768398262690), revertCode); + + // Deploy Utility - checkTstoreAvailable() returns false, so the activation + // logic is skipped entirely and TSTORE_INITIAL_SUPPORT stays false + UtilityTestHarness harness = new UtilityTestHarness(); + + // TSTORE_INITIAL_SUPPORT should be false because tstore is not available + assertFalse(harness.tstoreInitialSupport(), "TSTORE_INITIAL_SUPPORT should be false when tstore unavailable"); + + // Confirm _tstoreSupportActiveAt is 0 because no activation was attempted + bytes32 activeAtAfter = theCompact.extsload(bytes32(uint256(0))); + assertEq(uint256(activeAtAfter), 0, "_tstoreSupportActiveAt should be 0"); + } +} + +// ============= Mock Contracts ============= + +contract CheckBalanceDuringTransfer is ERC20 { + UtilityTestHarness private immutable _HARNESS; + + uint256 private id; + bool public afterTokenTransferActive; + + constructor(UtilityTestHarness harness_) { + _HARNESS = harness_; + _mint(msg.sender, 1e18); + } + + function _afterTokenTransfer(address from, address, uint256) internal view override { + if (afterTokenTransferActive) { + _HARNESS.exposed_settledBalanceOf(from, id); + } + } + + /// @dev Returns the name of the token. + function name() public pure override returns (string memory) { + return "CheckBalanceDuringTransfer"; + } + + /// @dev Returns the symbol of the token. + function symbol() public pure override returns (string memory) { + return "CBDT"; + } + + /// @dev Returns the decimals places of the token. + function decimals() public pure override returns (uint8) { + return 18; + } + + function setId(uint256 id_) public { + id = id_; + } + + function setAfterTokenTransferActive(bool active) public { + afterTokenTransferActive = active; + } +}