Recon Coverage Report 46% coverage
Files 87 files
    Files
    87
    Lines
    20219
    Coverage
    46%
    2435 / 5258
    Actions
    92% lib/chimera/src/CryticAsserts.sol
    Lines covered: 13 / 14 (92%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Asserts} from "./Asserts.sol";
    5
    6 0
    contract CryticAsserts is Asserts {
    7
        event Log(string);
    8
    9
        function gt(uint256 a, uint256 b, string memory reason) internal virtual override {
    10
            if (!(a > b)) {
    11
                emit Log(reason);
    12
                assert(false);
    13
            }
    14
        }
    15
    16
        function gte(uint256 a, uint256 b, string memory reason) internal virtual override {
    17
            if (!(a >= b)) {
    18
                emit Log(reason);
    19
                assert(false);
    20
            }
    21
        }
    22
    23
        function lt(uint256 a, uint256 b, string memory reason) internal virtual override {
    24
            if (!(a < b)) {
    25
                emit Log(reason);
    26
                assert(false);
    27
            }
    28
        }
    29
    30
        function lte(uint256 a, uint256 b, string memory reason) internal virtual override {
    31
            if (!(a <= b)) {
    32 17×
                emit Log(reason);
    33
                assert(false);
    34
            }
    35
        }
    36
    37
        function eq(uint256 a, uint256 b, string memory reason) internal virtual override {
    38
            if (!(a == b)) {
    39
                emit Log(reason);
    40
                assert(false);
    41
            }
    42
        }
    43
    44
        function t(bool b, string memory reason) internal virtual override {
    45
            if (!b) {
    46 34×
                emit Log(reason);
    47
                assert(false);
    48
            }
    49
        }
    50
    51 18×
        function between(uint256 value, uint256 low, uint256 high) internal virtual override returns (uint256) {
    52 30×
            if (value < low || value > high) {
    53 52×
                uint256 ans = low + (value % (high - low + 1));
    54
                return ans;
    55
            }
    56
            return value;
    57
        }
    58
    59
        function between(int256 value, int256 low, int256 high) internal virtual override returns (int256) {
    60
            if (value < low || value > high) {
    61
                int256 range = high - low + 1;
    62
                int256 clamped = (value - low) % (range);
    63
                if (clamped < 0) clamped += range;
    64
                int256 ans = low + clamped;
    65
                return ans;
    66
            }
    67
            return value;
    68
        }
    69
    70
        function precondition(bool p) internal virtual override {
    71
            require(p);
    72
        }
    73
    }
    50% lib/forge-std/src/Base.sol
    Lines covered: 1 / 2 (50%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    import {StdStorage} from "./StdStorage.sol";
    5
    import {Vm, VmSafe} from "./Vm.sol";
    6
    7
    abstract contract CommonBase {
    8
        /// @dev Cheat code address.
    9
        /// Calculated as `address(uint160(uint256(keccak256("hevm cheat code"))))`.
    10 21×
        address internal constant VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
    11
        /// @dev console.sol and console2.sol work by executing a staticcall to this address.
    12
        /// Calculated as `address(uint160(uint88(bytes11("console.log"))))`.
    13
        address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67;
    14
        /// @dev Used when deploying with create2.
    15
        /// Taken from https://github.com/Arachnid/deterministic-deployment-proxy.
    16
        address internal constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
    17
        /// @dev The default address for tx.origin and msg.sender.
    18
        /// Calculated as `address(uint160(uint256(keccak256("foundry default caller"))))`.
    19
        address internal constant DEFAULT_SENDER = 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38;
    20
        /// @dev The address of the first contract `CREATE`d by a running test contract.
    21
        /// When running tests, each test contract is `CREATE`d by `DEFAULT_SENDER` with nonce 1.
    22
        /// Calculated as `VM.computeCreateAddress(VM.computeCreateAddress(DEFAULT_SENDER, 1), 1)`.
    23
        address internal constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f;
    24
        /// @dev Deterministic deployment address of the Multicall3 contract.
    25
        /// Taken from https://www.multicall3.com.
    26
        address internal constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11;
    27
        /// @dev The order of the secp256k1 curve.
    28
        uint256 internal constant SECP256K1_ORDER =
    29
            115792089237316195423570985008687907852837564279074904382605163141518161494337;
    30
    31
        uint256 internal constant UINT256_MAX =
    32 0
            115792089237316195423570985008687907853269984665640564039457584007913129639935;
    33
    34
        Vm internal constant vm = Vm(VM_ADDRESS);
    35
        StdStorage internal stdstore;
    36
    }
    37
    38
    abstract contract TestBase is CommonBase {}
    39
    40
    abstract contract ScriptBase is CommonBase {
    41
        VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS);
    42
    }
    100% lib/forge-std/src/StdChains.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    import {VmSafe} from "./Vm.sol";
    5
    6
    /**
    7
     * StdChains provides information about EVM compatible chains that can be used in scripts/tests.
    8
     * For each chain, the chain's name, chain ID, and a default RPC URL are provided. Chains are
    9
     * identified by their alias, which is the same as the alias in the `[rpc_endpoints]` section of
    10
     * the `foundry.toml` file. For best UX, ensure the alias in the `foundry.toml` file match the
    11
     * alias used in this contract, which can be found as the first argument to the
    12
     * `setChainWithDefaultRpcUrl` call in the `initializeStdChains` function.
    13
     *
    14
     * There are two main ways to use this contract:
    15
     *   1. Set a chain with `setChain(string memory chainAlias, ChainData memory chain)` or
    16
     *      `setChain(string memory chainAlias, Chain memory chain)`
    17
     *   2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
    18
     *
    19
     * The first time either of those are used, chains are initialized with the default set of RPC URLs.
    20
     * This is done in `initializeStdChains`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in
    21
     * `defaultRpcUrls`.
    22
     *
    23
     * The `setChain` function is straightforward, and it simply saves off the given chain data.
    24
     *
    25
     * The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
    26
     * we want to retrieve the RPC URL for `mainnet`:
    27
     *   - If you have specified data with `setChain`, it will return that.
    28
     *   - If you have configured a mainnet RPC URL in `foundry.toml`, it will return the URL, provided it
    29
     *     is valid (e.g. a URL is specified, or an environment variable is given and exists).
    30
     *   - If neither of the above conditions is met, the default data is returned.
    31
     *
    32
     * Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> environment variable -> defaults.
    33
     */
    34
    abstract contract StdChains {
    35
        VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
    36
    37
        bool private stdChainsInitialized;
    38
    39
        struct ChainData {
    40
            string name;
    41
            uint256 chainId;
    42
            string rpcUrl;
    43
        }
    44
    45
        struct Chain {
    46
            // The chain name.
    47
            string name;
    48
            // The chain's Chain ID.
    49
            uint256 chainId;
    50
            // The chain's alias. (i.e. what gets specified in `foundry.toml`).
    51
            string chainAlias;
    52
            // A default RPC endpoint for this chain.
    53
            // NOTE: This default RPC URL is included for convenience to facilitate quick tests and
    54
            // experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
    55
            // usage as you will be throttled and this is a disservice to others who need this endpoint.
    56
            string rpcUrl;
    57
        }
    58
    59
        // Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data.
    60
        mapping(string => Chain) private chains;
    61
        // Maps from the chain's alias to it's default RPC URL.
    62
        mapping(string => string) private defaultRpcUrls;
    63
        // Maps from a chain ID to it's alias.
    64
        mapping(uint256 => string) private idToAlias;
    65
    66 12×
        bool private fallbackToDefaultRpcUrls = true;
    67
    68
        // The RPC URL will be fetched from config or defaultRpcUrls if possible.
    69
        function getChain(string memory chainAlias) internal virtual returns (Chain memory chain) {
    70
            require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string.");
    71
    72
            initializeStdChains();
    73
            chain = chains[chainAlias];
    74
            require(
    75
                chain.chainId != 0,
    76
                string(abi.encodePacked("StdChains getChain(string): Chain with alias \"", chainAlias, "\" not found."))
    77
            );
    78
    79
            chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
    80
        }
    81
    82
        function getChain(uint256 chainId) internal virtual returns (Chain memory chain) {
    83
            require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0.");
    84
            initializeStdChains();
    85
            string memory chainAlias = idToAlias[chainId];
    86
    87
            chain = chains[chainAlias];
    88
    89
            require(
    90
                chain.chainId != 0,
    91
                string(abi.encodePacked("StdChains getChain(uint256): Chain with ID ", vm.toString(chainId), " not found."))
    92
            );
    93
    94
            chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
    95
        }
    96
    97
        // set chain info, with priority to argument's rpcUrl field.
    98
        function setChain(string memory chainAlias, ChainData memory chain) internal virtual {
    99
            require(
    100
                bytes(chainAlias).length != 0,
    101
                "StdChains setChain(string,ChainData): Chain alias cannot be the empty string."
    102
            );
    103
    104
            require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");
    105
    106
            initializeStdChains();
    107
            string memory foundAlias = idToAlias[chain.chainId];
    108
    109
            require(
    110
                bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),
    111
                string(
    112
                    abi.encodePacked(
    113
                        "StdChains setChain(string,ChainData): Chain ID ",
    114
                        vm.toString(chain.chainId),
    115
                        " already used by \"",
    116
                        foundAlias,
    117
                        "\"."
    118
                    )
    119
                )
    120
            );
    121
    122
            uint256 oldChainId = chains[chainAlias].chainId;
    123
            delete idToAlias[oldChainId];
    124
    125
            chains[chainAlias] =
    126
                Chain({name: chain.name, chainId: chain.chainId, chainAlias: chainAlias, rpcUrl: chain.rpcUrl});
    127
            idToAlias[chain.chainId] = chainAlias;
    128
        }
    129
    130
        // set chain info, with priority to argument's rpcUrl field.
    131
        function setChain(string memory chainAlias, Chain memory chain) internal virtual {
    132
            setChain(chainAlias, ChainData({name: chain.name, chainId: chain.chainId, rpcUrl: chain.rpcUrl}));
    133
        }
    134
    135
        function _toUpper(string memory str) private pure returns (string memory) {
    136
            bytes memory strb = bytes(str);
    137
            bytes memory copy = new bytes(strb.length);
    138
            for (uint256 i = 0; i < strb.length; i++) {
    139
                bytes1 b = strb[i];
    140
                if (b >= 0x61 && b <= 0x7A) {
    141
                    copy[i] = bytes1(uint8(b) - 32);
    142
                } else {
    143
                    copy[i] = b;
    144
                }
    145
            }
    146
            return string(copy);
    147
        }
    148
    149
        // lookup rpcUrl, in descending order of priority:
    150
        // current -> config (foundry.toml) -> environment variable -> default
    151
        function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain)
    152
            private
    153
            view
    154
            returns (Chain memory)
    155
        {
    156
            if (bytes(chain.rpcUrl).length == 0) {
    157
                try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) {
    158
                    chain.rpcUrl = configRpcUrl;
    159
                } catch (bytes memory err) {
    160
                    string memory envName = string(abi.encodePacked(_toUpper(chainAlias), "_RPC_URL"));
    161
                    if (fallbackToDefaultRpcUrls) {
    162
                        chain.rpcUrl = vm.envOr(envName, defaultRpcUrls[chainAlias]);
    163
                    } else {
    164
                        chain.rpcUrl = vm.envString(envName);
    165
                    }
    166
                    // Distinguish 'not found' from 'cannot read'
    167
                    // The upstream error thrown by forge for failing cheats changed so we check both the old and new versions
    168
                    bytes memory oldNotFoundError =
    169
                        abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
    170
                    bytes memory newNotFoundError = abi.encodeWithSignature(
    171
                        "CheatcodeError(string)", string(abi.encodePacked("invalid rpc url: ", chainAlias))
    172
                    );
    173
                    bytes32 errHash = keccak256(err);
    174
                    if (
    175
                        (errHash != keccak256(oldNotFoundError) && errHash != keccak256(newNotFoundError))
    176
                            || bytes(chain.rpcUrl).length == 0
    177
                    ) {
    178
                        /// @solidity memory-safe-assembly
    179
                        assembly {
    180
                            revert(add(32, err), mload(err))
    181
                        }
    182
                    }
    183
                }
    184
            }
    185
            return chain;
    186
        }
    187
    188
        function setFallbackToDefaultRpcUrls(bool useDefault) internal {
    189
            fallbackToDefaultRpcUrls = useDefault;
    190
        }
    191
    192
        function initializeStdChains() private {
    193
            if (stdChainsInitialized) return;
    194
    195
            stdChainsInitialized = true;
    196
    197
            // If adding an RPC here, make sure to test the default RPC URL in `test_Rpcs` in `StdChains.t.sol`
    198
            setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));
    199
            setChainWithDefaultRpcUrl("mainnet", ChainData("Mainnet", 1, "https://eth.llamarpc.com"));
    200
            setChainWithDefaultRpcUrl(
    201
                "sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
    202
            );
    203
            setChainWithDefaultRpcUrl("holesky", ChainData("Holesky", 17000, "https://rpc.holesky.ethpandaops.io"));
    204
            setChainWithDefaultRpcUrl("hoodi", ChainData("Hoodi", 560048, "https://rpc.hoodi.ethpandaops.io"));
    205
            setChainWithDefaultRpcUrl("optimism", ChainData("Optimism", 10, "https://mainnet.optimism.io"));
    206
            setChainWithDefaultRpcUrl(
    207
                "optimism_sepolia", ChainData("Optimism Sepolia", 11155420, "https://sepolia.optimism.io")
    208
            );
    209
            setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
    210
            setChainWithDefaultRpcUrl(
    211
                "arbitrum_one_sepolia", ChainData("Arbitrum One Sepolia", 421614, "https://sepolia-rollup.arbitrum.io/rpc")
    212
            );
    213
            setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
    214
            setChainWithDefaultRpcUrl("polygon", ChainData("Polygon", 137, "https://polygon-rpc.com"));
    215
            setChainWithDefaultRpcUrl(
    216
                "polygon_amoy", ChainData("Polygon Amoy", 80002, "https://rpc-amoy.polygon.technology")
    217
            );
    218
            setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
    219
            setChainWithDefaultRpcUrl(
    220
                "avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
    221
            );
    222
            setChainWithDefaultRpcUrl(
    223
                "bnb_smart_chain", ChainData("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")
    224
            );
    225
            setChainWithDefaultRpcUrl(
    226
                "bnb_smart_chain_testnet",
    227
                ChainData("BNB Smart Chain Testnet", 97, "https://rpc.ankr.com/bsc_testnet_chapel")
    228
            );
    229
            setChainWithDefaultRpcUrl("gnosis_chain", ChainData("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
    230
            setChainWithDefaultRpcUrl("moonbeam", ChainData("Moonbeam", 1284, "https://rpc.api.moonbeam.network"));
    231
            setChainWithDefaultRpcUrl(
    232
                "moonriver", ChainData("Moonriver", 1285, "https://rpc.api.moonriver.moonbeam.network")
    233
            );
    234
            setChainWithDefaultRpcUrl("moonbase", ChainData("Moonbase", 1287, "https://rpc.testnet.moonbeam.network"));
    235
            setChainWithDefaultRpcUrl("base_sepolia", ChainData("Base Sepolia", 84532, "https://sepolia.base.org"));
    236
            setChainWithDefaultRpcUrl("base", ChainData("Base", 8453, "https://mainnet.base.org"));
    237
            setChainWithDefaultRpcUrl("blast_sepolia", ChainData("Blast Sepolia", 168587773, "https://sepolia.blast.io"));
    238
            setChainWithDefaultRpcUrl("blast", ChainData("Blast", 81457, "https://rpc.blast.io"));
    239
            setChainWithDefaultRpcUrl("fantom_opera", ChainData("Fantom Opera", 250, "https://rpc.ankr.com/fantom/"));
    240
            setChainWithDefaultRpcUrl(
    241
                "fantom_opera_testnet", ChainData("Fantom Opera Testnet", 4002, "https://rpc.ankr.com/fantom_testnet/")
    242
            );
    243
            setChainWithDefaultRpcUrl("fraxtal", ChainData("Fraxtal", 252, "https://rpc.frax.com"));
    244
            setChainWithDefaultRpcUrl("fraxtal_testnet", ChainData("Fraxtal Testnet", 2522, "https://rpc.testnet.frax.com"));
    245
            setChainWithDefaultRpcUrl(
    246
                "berachain_bartio_testnet", ChainData("Berachain bArtio Testnet", 80084, "https://bartio.rpc.berachain.com")
    247
            );
    248
            setChainWithDefaultRpcUrl("flare", ChainData("Flare", 14, "https://flare-api.flare.network/ext/C/rpc"));
    249
            setChainWithDefaultRpcUrl(
    250
                "flare_coston2", ChainData("Flare Coston2", 114, "https://coston2-api.flare.network/ext/C/rpc")
    251
            );
    252
    253
            setChainWithDefaultRpcUrl("mode", ChainData("Mode", 34443, "https://mode.drpc.org"));
    254
            setChainWithDefaultRpcUrl("mode_sepolia", ChainData("Mode Sepolia", 919, "https://sepolia.mode.network"));
    255
    256
            setChainWithDefaultRpcUrl("zora", ChainData("Zora", 7777777, "https://zora.drpc.org"));
    257
            setChainWithDefaultRpcUrl(
    258
                "zora_sepolia", ChainData("Zora Sepolia", 999999999, "https://sepolia.rpc.zora.energy")
    259
            );
    260
    261
            setChainWithDefaultRpcUrl("race", ChainData("Race", 6805, "https://racemainnet.io"));
    262
            setChainWithDefaultRpcUrl("race_sepolia", ChainData("Race Sepolia", 6806, "https://racemainnet.io"));
    263
    264
            setChainWithDefaultRpcUrl("metal", ChainData("Metal", 1750, "https://metall2.drpc.org"));
    265
            setChainWithDefaultRpcUrl("metal_sepolia", ChainData("Metal Sepolia", 1740, "https://testnet.rpc.metall2.com"));
    266
    267
            setChainWithDefaultRpcUrl("binary", ChainData("Binary", 624, "https://rpc.zero.thebinaryholdings.com"));
    268
            setChainWithDefaultRpcUrl(
    269
                "binary_sepolia", ChainData("Binary Sepolia", 625, "https://rpc.zero.thebinaryholdings.com")
    270
            );
    271
    272
            setChainWithDefaultRpcUrl("orderly", ChainData("Orderly", 291, "https://rpc.orderly.network"));
    273
            setChainWithDefaultRpcUrl(
    274
                "orderly_sepolia", ChainData("Orderly Sepolia", 4460, "https://testnet-rpc.orderly.org")
    275
            );
    276
        }
    277
    278
        // set chain info, with priority to chainAlias' rpc url in foundry.toml
    279
        function setChainWithDefaultRpcUrl(string memory chainAlias, ChainData memory chain) private {
    280
            string memory rpcUrl = chain.rpcUrl;
    281
            defaultRpcUrls[chainAlias] = rpcUrl;
    282
            chain.rpcUrl = "";
    283
            setChain(chainAlias, chain);
    284
            chain.rpcUrl = rpcUrl; // restore argument
    285
        }
    286
    }
    10% lib/forge-std/src/StdCheats.sol
    Lines covered: 6 / 55 (10%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    pragma experimental ABIEncoderV2;
    5
    6
    import {StdStorage, stdStorage} from "./StdStorage.sol";
    7
    import {console2} from "./console2.sol";
    8
    import {Vm} from "./Vm.sol";
    9
    10
    abstract contract StdCheatsSafe {
    11
        Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
    12
    13
        uint256 private constant UINT256_MAX =
    14
            115792089237316195423570985008687907853269984665640564039457584007913129639935;
    15
    16
        bool private gasMeteringOff;
    17
    18
        // Data structures to parse Transaction objects from the broadcast artifact
    19
        // that conform to EIP1559. The Raw structs is what is parsed from the JSON
    20
        // and then converted to the one that is used by the user for better UX.
    21
    22
        struct RawTx1559 {
    23
            string[] arguments;
    24
            address contractAddress;
    25
            string contractName;
    26
            // json value name = function
    27
            string functionSig;
    28
            bytes32 hash;
    29
            // json value name = tx
    30
            RawTx1559Detail txDetail;
    31
            // json value name = type
    32
            string opcode;
    33
        }
    34
    35
        struct RawTx1559Detail {
    36
            AccessList[] accessList;
    37
            bytes data;
    38
            address from;
    39
            bytes gas;
    40
            bytes nonce;
    41
            address to;
    42
            bytes txType;
    43
            bytes value;
    44
        }
    45
    46
        struct Tx1559 {
    47
            string[] arguments;
    48
            address contractAddress;
    49
            string contractName;
    50
            string functionSig;
    51
            bytes32 hash;
    52
            Tx1559Detail txDetail;
    53
            string opcode;
    54
        }
    55
    56
        struct Tx1559Detail {
    57
            AccessList[] accessList;
    58
            bytes data;
    59
            address from;
    60
            uint256 gas;
    61
            uint256 nonce;
    62
            address to;
    63
            uint256 txType;
    64
            uint256 value;
    65
        }
    66
    67
        // Data structures to parse Transaction objects from the broadcast artifact
    68
        // that DO NOT conform to EIP1559. The Raw structs is what is parsed from the JSON
    69
        // and then converted to the one that is used by the user for better UX.
    70
    71
        struct TxLegacy {
    72
            string[] arguments;
    73
            address contractAddress;
    74
            string contractName;
    75
            string functionSig;
    76
            string hash;
    77
            string opcode;
    78
            TxDetailLegacy transaction;
    79
        }
    80
    81
        struct TxDetailLegacy {
    82
            AccessList[] accessList;
    83
            uint256 chainId;
    84
            bytes data;
    85
            address from;
    86
            uint256 gas;
    87
            uint256 gasPrice;
    88
            bytes32 hash;
    89
            uint256 nonce;
    90
            bytes1 opcode;
    91
            bytes32 r;
    92
            bytes32 s;
    93
            uint256 txType;
    94
            address to;
    95
            uint8 v;
    96
            uint256 value;
    97
        }
    98
    99
        struct AccessList {
    100
            address accessAddress;
    101
            bytes32[] storageKeys;
    102
        }
    103
    104
        // Data structures to parse Receipt objects from the broadcast artifact.
    105
        // The Raw structs is what is parsed from the JSON
    106
        // and then converted to the one that is used by the user for better UX.
    107
    108
        struct RawReceipt {
    109
            bytes32 blockHash;
    110
            bytes blockNumber;
    111
            address contractAddress;
    112
            bytes cumulativeGasUsed;
    113
            bytes effectiveGasPrice;
    114
            address from;
    115
            bytes gasUsed;
    116
            RawReceiptLog[] logs;
    117
            bytes logsBloom;
    118
            bytes status;
    119
            address to;
    120
            bytes32 transactionHash;
    121
            bytes transactionIndex;
    122
        }
    123
    124
        struct Receipt {
    125
            bytes32 blockHash;
    126
            uint256 blockNumber;
    127
            address contractAddress;
    128
            uint256 cumulativeGasUsed;
    129
            uint256 effectiveGasPrice;
    130
            address from;
    131
            uint256 gasUsed;
    132
            ReceiptLog[] logs;
    133
            bytes logsBloom;
    134
            uint256 status;
    135
            address to;
    136
            bytes32 transactionHash;
    137
            uint256 transactionIndex;
    138
        }
    139
    140
        // Data structures to parse the entire broadcast artifact, assuming the
    141
        // transactions conform to EIP1559.
    142
    143
        struct EIP1559ScriptArtifact {
    144
            string[] libraries;
    145
            string path;
    146
            string[] pending;
    147
            Receipt[] receipts;
    148
            uint256 timestamp;
    149
            Tx1559[] transactions;
    150
            TxReturn[] txReturns;
    151
        }
    152
    153
        struct RawEIP1559ScriptArtifact {
    154
            string[] libraries;
    155
            string path;
    156
            string[] pending;
    157
            RawReceipt[] receipts;
    158
            TxReturn[] txReturns;
    159
            uint256 timestamp;
    160
            RawTx1559[] transactions;
    161
        }
    162
    163
        struct RawReceiptLog {
    164
            // json value = address
    165
            address logAddress;
    166
            bytes32 blockHash;
    167
            bytes blockNumber;
    168
            bytes data;
    169
            bytes logIndex;
    170
            bool removed;
    171
            bytes32[] topics;
    172
            bytes32 transactionHash;
    173
            bytes transactionIndex;
    174
            bytes transactionLogIndex;
    175
        }
    176
    177
        struct ReceiptLog {
    178
            // json value = address
    179
            address logAddress;
    180
            bytes32 blockHash;
    181
            uint256 blockNumber;
    182
            bytes data;
    183
            uint256 logIndex;
    184
            bytes32[] topics;
    185
            uint256 transactionIndex;
    186
            uint256 transactionLogIndex;
    187
            bool removed;
    188
        }
    189
    190
        struct TxReturn {
    191
            string internalType;
    192
            string value;
    193
        }
    194
    195
        struct Account {
    196
            address addr;
    197
            uint256 key;
    198
        }
    199
    200
        enum AddressType {
    201
            Payable,
    202
            NonPayable,
    203
            ZeroAddress,
    204
            Precompile,
    205
            ForgeAddress
    206
        }
    207
    208
        // Checks that `addr` is not blacklisted by token contracts that have a blacklist.
    209
        function assumeNotBlacklisted(address token, address addr) internal view virtual {
    210
            // Nothing to check if `token` is not a contract.
    211
            uint256 tokenCodeSize;
    212
            assembly {
    213
                tokenCodeSize := extcodesize(token)
    214
            }
    215
            require(tokenCodeSize > 0, "StdCheats assumeNotBlacklisted(address,address): Token address is not a contract.");
    216
    217
            bool success;
    218
            bytes memory returnData;
    219
    220
            // 4-byte selector for `isBlacklisted(address)`, used by USDC.
    221
            (success, returnData) = token.staticcall(abi.encodeWithSelector(0xfe575a87, addr));
    222
            vm.assume(!success || abi.decode(returnData, (bool)) == false);
    223
    224
            // 4-byte selector for `isBlackListed(address)`, used by USDT.
    225
            (success, returnData) = token.staticcall(abi.encodeWithSelector(0xe47d6060, addr));
    226
            vm.assume(!success || abi.decode(returnData, (bool)) == false);
    227
        }
    228
    229
        // Checks that `addr` is not blacklisted by token contracts that have a blacklist.
    230
        // This is identical to `assumeNotBlacklisted(address,address)` but with a different name, for
    231
        // backwards compatibility, since this name was used in the original PR which already has
    232
        // a release. This function can be removed in a future release once we want a breaking change.
    233
        function assumeNoBlacklisted(address token, address addr) internal view virtual {
    234
            assumeNotBlacklisted(token, addr);
    235
        }
    236
    237
        function assumeAddressIsNot(address addr, AddressType addressType) internal virtual {
    238
            if (addressType == AddressType.Payable) {
    239
                assumeNotPayable(addr);
    240
            } else if (addressType == AddressType.NonPayable) {
    241
                assumePayable(addr);
    242
            } else if (addressType == AddressType.ZeroAddress) {
    243
                assumeNotZeroAddress(addr);
    244
            } else if (addressType == AddressType.Precompile) {
    245
                assumeNotPrecompile(addr);
    246
            } else if (addressType == AddressType.ForgeAddress) {
    247
                assumeNotForgeAddress(addr);
    248
            }
    249
        }
    250
    251
        function assumeAddressIsNot(address addr, AddressType addressType1, AddressType addressType2) internal virtual {
    252
            assumeAddressIsNot(addr, addressType1);
    253
            assumeAddressIsNot(addr, addressType2);
    254
        }
    255
    256
        function assumeAddressIsNot(
    257
            address addr,
    258
            AddressType addressType1,
    259
            AddressType addressType2,
    260
            AddressType addressType3
    261
        ) internal virtual {
    262
            assumeAddressIsNot(addr, addressType1);
    263
            assumeAddressIsNot(addr, addressType2);
    264
            assumeAddressIsNot(addr, addressType3);
    265
        }
    266
    267
        function assumeAddressIsNot(
    268
            address addr,
    269
            AddressType addressType1,
    270
            AddressType addressType2,
    271
            AddressType addressType3,
    272
            AddressType addressType4
    273
        ) internal virtual {
    274
            assumeAddressIsNot(addr, addressType1);
    275
            assumeAddressIsNot(addr, addressType2);
    276
            assumeAddressIsNot(addr, addressType3);
    277
            assumeAddressIsNot(addr, addressType4);
    278
        }
    279
    280
        // This function checks whether an address, `addr`, is payable. It works by sending 1 wei to
    281
        // `addr` and checking the `success` return value.
    282
        // NOTE: This function may result in state changes depending on the fallback/receive logic
    283
        // implemented by `addr`, which should be taken into account when this function is used.
    284
        function _isPayable(address addr) private returns (bool) {
    285
            require(
    286
                addr.balance < UINT256_MAX,
    287
                "StdCheats _isPayable(address): Balance equals max uint256, so it cannot receive any more funds"
    288
            );
    289
            uint256 origBalanceTest = address(this).balance;
    290
            uint256 origBalanceAddr = address(addr).balance;
    291
    292
            vm.deal(address(this), 1);
    293
            (bool success,) = payable(addr).call{value: 1}("");
    294
    295
            // reset balances
    296
            vm.deal(address(this), origBalanceTest);
    297
            vm.deal(addr, origBalanceAddr);
    298
    299
            return success;
    300
        }
    301
    302
        // NOTE: This function may result in state changes depending on the fallback/receive logic
    303
        // implemented by `addr`, which should be taken into account when this function is used. See the
    304
        // `_isPayable` method for more information.
    305
        function assumePayable(address addr) internal virtual {
    306
            vm.assume(_isPayable(addr));
    307
        }
    308
    309
        function assumeNotPayable(address addr) internal virtual {
    310
            vm.assume(!_isPayable(addr));
    311
        }
    312
    313 0
        function assumeNotZeroAddress(address addr) internal pure virtual {
    314 0
            vm.assume(addr != address(0));
    315
        }
    316
    317 0
        function assumeNotPrecompile(address addr) internal pure virtual {
    318 0
            assumeNotPrecompile(addr, _pureChainId());
    319
        }
    320
    321 0
        function assumeNotPrecompile(address addr, uint256 chainId) internal pure virtual {
    322
            // Note: For some chains like Optimism these are technically predeploys (i.e. bytecode placed at a specific
    323
            // address), but the same rationale for excluding them applies so we include those too.
    324
    325
            // These are reserved by Ethereum and may be on all EVM-compatible chains.
    326 0
            vm.assume(addr < address(0x1) || addr > address(0xff));
    327
    328
            // forgefmt: disable-start
    329 0
            if (chainId == 10 || chainId == 420) {
    330
                // https://github.com/ethereum-optimism/optimism/blob/eaa371a0184b56b7ca6d9eb9cb0a2b78b2ccd864/op-bindings/predeploys/addresses.go#L6-L21
    331 0
                vm.assume(addr < address(0x4200000000000000000000000000000000000000) || addr > address(0x4200000000000000000000000000000000000800));
    332 0
            } else if (chainId == 42161 || chainId == 421613) {
    333
                // https://developer.arbitrum.io/useful-addresses#arbitrum-precompiles-l2-same-on-all-arb-chains
    334 0
                vm.assume(addr < address(0x0000000000000000000000000000000000000064) || addr > address(0x0000000000000000000000000000000000000068));
    335 0
            } else if (chainId == 43114 || chainId == 43113) {
    336
                // https://github.com/ava-labs/subnet-evm/blob/47c03fd007ecaa6de2c52ea081596e0a88401f58/precompile/params.go#L18-L59
    337 0
                vm.assume(addr < address(0x0100000000000000000000000000000000000000) || addr > address(0x01000000000000000000000000000000000000ff));
    338 0
                vm.assume(addr < address(0x0200000000000000000000000000000000000000) || addr > address(0x02000000000000000000000000000000000000FF));
    339 0
                vm.assume(addr < address(0x0300000000000000000000000000000000000000) || addr > address(0x03000000000000000000000000000000000000Ff));
    340
            }
    341
            // forgefmt: disable-end
    342
        }
    343
    344 0
        function assumeNotForgeAddress(address addr) internal pure virtual {
    345
            // vm, console, and Create2Deployer addresses
    346 0
            vm.assume(
    347 0
                addr != address(vm) && addr != 0x000000000000000000636F6e736F6c652e6c6f67
    348 0
                    && addr != 0x4e59b44847b379578588920cA78FbF26c0B4956C
    349
            );
    350
        }
    351
    352 0
        function assumeUnusedAddress(address addr) internal view virtual {
    353
            uint256 size;
    354
            assembly {
    355 0
                size := extcodesize(addr)
    356
            }
    357 0
            vm.assume(size == 0);
    358
    359 0
            assumeNotPrecompile(addr);
    360 0
            assumeNotZeroAddress(addr);
    361 0
            assumeNotForgeAddress(addr);
    362
        }
    363
    364
        function readEIP1559ScriptArtifact(string memory path)
    365
            internal
    366
            view
    367
            virtual
    368
            returns (EIP1559ScriptArtifact memory)
    369
        {
    370
            string memory data = vm.readFile(path);
    371
            bytes memory parsedData = vm.parseJson(data);
    372
            RawEIP1559ScriptArtifact memory rawArtifact = abi.decode(parsedData, (RawEIP1559ScriptArtifact));
    373
            EIP1559ScriptArtifact memory artifact;
    374
            artifact.libraries = rawArtifact.libraries;
    375
            artifact.path = rawArtifact.path;
    376
            artifact.timestamp = rawArtifact.timestamp;
    377
            artifact.pending = rawArtifact.pending;
    378
            artifact.txReturns = rawArtifact.txReturns;
    379
            artifact.receipts = rawToConvertedReceipts(rawArtifact.receipts);
    380
            artifact.transactions = rawToConvertedEIPTx1559s(rawArtifact.transactions);
    381
            return artifact;
    382
        }
    383
    384
        function rawToConvertedEIPTx1559s(RawTx1559[] memory rawTxs) internal pure virtual returns (Tx1559[] memory) {
    385
            Tx1559[] memory txs = new Tx1559[](rawTxs.length);
    386
            for (uint256 i; i < rawTxs.length; i++) {
    387
                txs[i] = rawToConvertedEIPTx1559(rawTxs[i]);
    388
            }
    389
            return txs;
    390
        }
    391
    392
        function rawToConvertedEIPTx1559(RawTx1559 memory rawTx) internal pure virtual returns (Tx1559 memory) {
    393
            Tx1559 memory transaction;
    394
            transaction.arguments = rawTx.arguments;
    395
            transaction.contractName = rawTx.contractName;
    396
            transaction.functionSig = rawTx.functionSig;
    397
            transaction.hash = rawTx.hash;
    398
            transaction.txDetail = rawToConvertedEIP1559Detail(rawTx.txDetail);
    399
            transaction.opcode = rawTx.opcode;
    400
            return transaction;
    401
        }
    402
    403
        function rawToConvertedEIP1559Detail(RawTx1559Detail memory rawDetail)
    404
            internal
    405
            pure
    406
            virtual
    407
            returns (Tx1559Detail memory)
    408
        {
    409
            Tx1559Detail memory txDetail;
    410
            txDetail.data = rawDetail.data;
    411
            txDetail.from = rawDetail.from;
    412
            txDetail.to = rawDetail.to;
    413
            txDetail.nonce = _bytesToUint(rawDetail.nonce);
    414
            txDetail.txType = _bytesToUint(rawDetail.txType);
    415
            txDetail.value = _bytesToUint(rawDetail.value);
    416
            txDetail.gas = _bytesToUint(rawDetail.gas);
    417
            txDetail.accessList = rawDetail.accessList;
    418
            return txDetail;
    419
        }
    420
    421
        function readTx1559s(string memory path) internal view virtual returns (Tx1559[] memory) {
    422
            string memory deployData = vm.readFile(path);
    423
            bytes memory parsedDeployData = vm.parseJson(deployData, ".transactions");
    424
            RawTx1559[] memory rawTxs = abi.decode(parsedDeployData, (RawTx1559[]));
    425
            return rawToConvertedEIPTx1559s(rawTxs);
    426
        }
    427
    428
        function readTx1559(string memory path, uint256 index) internal view virtual returns (Tx1559 memory) {
    429
            string memory deployData = vm.readFile(path);
    430
            string memory key = string(abi.encodePacked(".transactions[", vm.toString(index), "]"));
    431
            bytes memory parsedDeployData = vm.parseJson(deployData, key);
    432
            RawTx1559 memory rawTx = abi.decode(parsedDeployData, (RawTx1559));
    433
            return rawToConvertedEIPTx1559(rawTx);
    434
        }
    435
    436
        // Analogous to readTransactions, but for receipts.
    437
        function readReceipts(string memory path) internal view virtual returns (Receipt[] memory) {
    438
            string memory deployData = vm.readFile(path);
    439
            bytes memory parsedDeployData = vm.parseJson(deployData, ".receipts");
    440
            RawReceipt[] memory rawReceipts = abi.decode(parsedDeployData, (RawReceipt[]));
    441
            return rawToConvertedReceipts(rawReceipts);
    442
        }
    443
    444
        function readReceipt(string memory path, uint256 index) internal view virtual returns (Receipt memory) {
    445
            string memory deployData = vm.readFile(path);
    446
            string memory key = string(abi.encodePacked(".receipts[", vm.toString(index), "]"));
    447
            bytes memory parsedDeployData = vm.parseJson(deployData, key);
    448
            RawReceipt memory rawReceipt = abi.decode(parsedDeployData, (RawReceipt));
    449
            return rawToConvertedReceipt(rawReceipt);
    450
        }
    451
    452
        function rawToConvertedReceipts(RawReceipt[] memory rawReceipts) internal pure virtual returns (Receipt[] memory) {
    453
            Receipt[] memory receipts = new Receipt[](rawReceipts.length);
    454
            for (uint256 i; i < rawReceipts.length; i++) {
    455
                receipts[i] = rawToConvertedReceipt(rawReceipts[i]);
    456
            }
    457
            return receipts;
    458
        }
    459
    460
        function rawToConvertedReceipt(RawReceipt memory rawReceipt) internal pure virtual returns (Receipt memory) {
    461
            Receipt memory receipt;
    462
            receipt.blockHash = rawReceipt.blockHash;
    463
            receipt.to = rawReceipt.to;
    464
            receipt.from = rawReceipt.from;
    465
            receipt.contractAddress = rawReceipt.contractAddress;
    466
            receipt.effectiveGasPrice = _bytesToUint(rawReceipt.effectiveGasPrice);
    467
            receipt.cumulativeGasUsed = _bytesToUint(rawReceipt.cumulativeGasUsed);
    468
            receipt.gasUsed = _bytesToUint(rawReceipt.gasUsed);
    469
            receipt.status = _bytesToUint(rawReceipt.status);
    470
            receipt.transactionIndex = _bytesToUint(rawReceipt.transactionIndex);
    471
            receipt.blockNumber = _bytesToUint(rawReceipt.blockNumber);
    472
            receipt.logs = rawToConvertedReceiptLogs(rawReceipt.logs);
    473
            receipt.logsBloom = rawReceipt.logsBloom;
    474
            receipt.transactionHash = rawReceipt.transactionHash;
    475
            return receipt;
    476
        }
    477
    478
        function rawToConvertedReceiptLogs(RawReceiptLog[] memory rawLogs)
    479
            internal
    480
            pure
    481
            virtual
    482
            returns (ReceiptLog[] memory)
    483
        {
    484
            ReceiptLog[] memory logs = new ReceiptLog[](rawLogs.length);
    485
            for (uint256 i; i < rawLogs.length; i++) {
    486
                logs[i].logAddress = rawLogs[i].logAddress;
    487
                logs[i].blockHash = rawLogs[i].blockHash;
    488
                logs[i].blockNumber = _bytesToUint(rawLogs[i].blockNumber);
    489
                logs[i].data = rawLogs[i].data;
    490
                logs[i].logIndex = _bytesToUint(rawLogs[i].logIndex);
    491
                logs[i].topics = rawLogs[i].topics;
    492
                logs[i].transactionIndex = _bytesToUint(rawLogs[i].transactionIndex);
    493
                logs[i].transactionLogIndex = _bytesToUint(rawLogs[i].transactionLogIndex);
    494
                logs[i].removed = rawLogs[i].removed;
    495
            }
    496
            return logs;
    497
        }
    498
    499
        // Deploy a contract by fetching the contract bytecode from
    500
        // the artifacts directory
    501
        // e.g. `deployCode(code, abi.encode(arg1,arg2,arg3))`
    502
        function deployCode(string memory what, bytes memory args) internal virtual returns (address addr) {
    503
            bytes memory bytecode = abi.encodePacked(vm.getCode(what), args);
    504
            /// @solidity memory-safe-assembly
    505
            assembly {
    506
                addr := create(0, add(bytecode, 0x20), mload(bytecode))
    507
            }
    508
    509
            require(addr != address(0), "StdCheats deployCode(string,bytes): Deployment failed.");
    510
        }
    511
    512
        function deployCode(string memory what) internal virtual returns (address addr) {
    513
            bytes memory bytecode = vm.getCode(what);
    514
            /// @solidity memory-safe-assembly
    515
            assembly {
    516
                addr := create(0, add(bytecode, 0x20), mload(bytecode))
    517
            }
    518
    519
            require(addr != address(0), "StdCheats deployCode(string): Deployment failed.");
    520
        }
    521
    522
        /// @dev deploy contract with value on construction
    523
        function deployCode(string memory what, bytes memory args, uint256 val) internal virtual returns (address addr) {
    524
            bytes memory bytecode = abi.encodePacked(vm.getCode(what), args);
    525
            /// @solidity memory-safe-assembly
    526
            assembly {
    527
                addr := create(val, add(bytecode, 0x20), mload(bytecode))
    528
            }
    529
    530
            require(addr != address(0), "StdCheats deployCode(string,bytes,uint256): Deployment failed.");
    531
        }
    532
    533
        function deployCode(string memory what, uint256 val) internal virtual returns (address addr) {
    534
            bytes memory bytecode = vm.getCode(what);
    535
            /// @solidity memory-safe-assembly
    536
            assembly {
    537
                addr := create(val, add(bytecode, 0x20), mload(bytecode))
    538
            }
    539
    540
            require(addr != address(0), "StdCheats deployCode(string,uint256): Deployment failed.");
    541
        }
    542
    543
        // creates a labeled address and the corresponding private key
    544 10×
        function makeAddrAndKey(string memory name) internal virtual returns (address addr, uint256 privateKey) {
    545 64×
            privateKey = uint256(keccak256(abi.encodePacked(name)));
    546 74×
            addr = vm.addr(privateKey);
    547 46×
            vm.label(addr, name);
    548
        }
    549
    550
        // creates a labeled address
    551
        function makeAddr(string memory name) internal virtual returns (address addr) {
    552 10×
            (addr,) = makeAddrAndKey(name);
    553
        }
    554
    555
        // Destroys an account immediately, sending the balance to beneficiary.
    556
        // Destroying means: balance will be zero, code will be empty, and nonce will be 0
    557
        // This is similar to selfdestruct but not identical: selfdestruct destroys code and nonce
    558
        // only after tx ends, this will run immediately.
    559
        function destroyAccount(address who, address beneficiary) internal virtual {
    560
            uint256 currBalance = who.balance;
    561
            vm.etch(who, abi.encode());
    562
            vm.deal(who, 0);
    563
            vm.resetNonce(who);
    564
    565
            uint256 beneficiaryBalance = beneficiary.balance;
    566
            vm.deal(beneficiary, currBalance + beneficiaryBalance);
    567
        }
    568
    569
        // creates a struct containing both a labeled address and the corresponding private key
    570
        function makeAccount(string memory name) internal virtual returns (Account memory account) {
    571
            (account.addr, account.key) = makeAddrAndKey(name);
    572
        }
    573
    574
        function deriveRememberKey(string memory mnemonic, uint32 index)
    575
            internal
    576
            virtual
    577
            returns (address who, uint256 privateKey)
    578
        {
    579
            privateKey = vm.deriveKey(mnemonic, index);
    580
            who = vm.rememberKey(privateKey);
    581
        }
    582
    583
        function _bytesToUint(bytes memory b) private pure returns (uint256) {
    584
            require(b.length <= 32, "StdCheats _bytesToUint(bytes): Bytes length exceeds 32.");
    585
            return abi.decode(abi.encodePacked(new bytes(32 - b.length), b), (uint256));
    586
        }
    587
    588
        function isFork() internal view virtual returns (bool status) {
    589
            try vm.activeFork() {
    590
                status = true;
    591
            } catch (bytes memory) {}
    592
        }
    593
    594
        modifier skipWhenForking() {
    595
            if (!isFork()) {
    596
                _;
    597
            }
    598
        }
    599
    600
        modifier skipWhenNotForking() {
    601
            if (isFork()) {
    602
                _;
    603
            }
    604
        }
    605
    606
        modifier noGasMetering() {
    607
            vm.pauseGasMetering();
    608
            // To prevent turning gas monitoring back on with nested functions that use this modifier,
    609
            // we check if gasMetering started in the off position. If it did, we don't want to turn
    610
            // it back on until we exit the top level function that used the modifier
    611
            //
    612
            // i.e. funcA() noGasMetering { funcB() }, where funcB has noGasMetering as well.
    613
            // funcA will have `gasStartedOff` as false, funcB will have it as true,
    614
            // so we only turn metering back on at the end of the funcA
    615
            bool gasStartedOff = gasMeteringOff;
    616
            gasMeteringOff = true;
    617
    618
            _;
    619
    620
            // if gas metering was on when this modifier was called, turn it back on at the end
    621
            if (!gasStartedOff) {
    622
                gasMeteringOff = false;
    623
                vm.resumeGasMetering();
    624
            }
    625
        }
    626
    627
        // We use this complex approach of `_viewChainId` and `_pureChainId` to ensure there are no
    628
        // compiler warnings when accessing chain ID in any solidity version supported by forge-std. We
    629
        // can't simply access the chain ID in a normal view or pure function because the solc View Pure
    630
        // Checker changed `chainid` from pure to view in 0.8.0.
    631 0
        function _viewChainId() private view returns (uint256 chainId) {
    632
            // Assembly required since `block.chainid` was introduced in 0.8.0.
    633
            assembly {
    634 0
                chainId := chainid()
    635
            }
    636
    637
            address(this); // Silence warnings in older Solc versions.
    638
        }
    639
    640 0
        function _pureChainId() private pure returns (uint256 chainId) {
    641 0
            function() internal view returns (uint256) fnIn = _viewChainId;
    642
            function() internal pure returns (uint256) pureChainId;
    643
            assembly {
    644
                pureChainId := fnIn
    645
            }
    646 0
            chainId = pureChainId();
    647
        }
    648
    }
    649
    650
    // Wrappers around cheatcodes to avoid footguns
    651
    abstract contract StdCheats is StdCheatsSafe {
    652
        using stdStorage for StdStorage;
    653
    654
        StdStorage private stdstore;
    655 0
        Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
    656
        address private constant CONSOLE2_ADDRESS = 0x000000000000000000636F6e736F6c652e6c6f67;
    657
    658
        // Skip forward or rewind time by the specified number of seconds
    659 0
        function skip(uint256 time) internal virtual {
    660 0
            vm.warp(vm.getBlockTimestamp() + time);
    661
        }
    662
    663 0
        function rewind(uint256 time) internal virtual {
    664 0
            vm.warp(vm.getBlockTimestamp() - time);
    665
        }
    666
    667
        // Setup a prank from an address that has some ether
    668
        function hoax(address msgSender) internal virtual {
    669
            vm.deal(msgSender, 1 << 128);
    670
            vm.prank(msgSender);
    671
        }
    672
    673
        function hoax(address msgSender, uint256 give) internal virtual {
    674
            vm.deal(msgSender, give);
    675
            vm.prank(msgSender);
    676
        }
    677
    678
        function hoax(address msgSender, address origin) internal virtual {
    679
            vm.deal(msgSender, 1 << 128);
    680
            vm.prank(msgSender, origin);
    681
        }
    682
    683
        function hoax(address msgSender, address origin, uint256 give) internal virtual {
    684
            vm.deal(msgSender, give);
    685
            vm.prank(msgSender, origin);
    686
        }
    687
    688
        // Start perpetual prank from an address that has some ether
    689
        function startHoax(address msgSender) internal virtual {
    690
            vm.deal(msgSender, 1 << 128);
    691
            vm.startPrank(msgSender);
    692
        }
    693
    694
        function startHoax(address msgSender, uint256 give) internal virtual {
    695
            vm.deal(msgSender, give);
    696
            vm.startPrank(msgSender);
    697
        }
    698
    699
        // Start perpetual prank from an address that has some ether
    700
        // tx.origin is set to the origin parameter
    701
        function startHoax(address msgSender, address origin) internal virtual {
    702
            vm.deal(msgSender, 1 << 128);
    703
            vm.startPrank(msgSender, origin);
    704
        }
    705
    706
        function startHoax(address msgSender, address origin, uint256 give) internal virtual {
    707
            vm.deal(msgSender, give);
    708
            vm.startPrank(msgSender, origin);
    709
        }
    710
    711
        function changePrank(address msgSender) internal virtual {
    712
            console2_log_StdCheats("changePrank is deprecated. Please use vm.startPrank instead.");
    713
            vm.stopPrank();
    714
            vm.startPrank(msgSender);
    715
        }
    716
    717
        function changePrank(address msgSender, address txOrigin) internal virtual {
    718
            vm.stopPrank();
    719
            vm.startPrank(msgSender, txOrigin);
    720
        }
    721
    722
        // The same as Vm's `deal`
    723
        // Use the alternative signature for ERC20 tokens
    724 0
        function deal(address to, uint256 give) internal virtual {
    725 0
            vm.deal(to, give);
    726
        }
    727
    728
        // Set the balance of an account for any ERC20 token
    729
        // Use the alternative signature to update `totalSupply`
    730 0
        function deal(address token, address to, uint256 give) internal virtual {
    731 0
            deal(token, to, give, false);
    732
        }
    733
    734
        // Set the balance of an account for any ERC1155 token
    735
        // Use the alternative signature to update `totalSupply`
    736
        function dealERC1155(address token, address to, uint256 id, uint256 give) internal virtual {
    737
            dealERC1155(token, to, id, give, false);
    738
        }
    739
    740 0
        function deal(address token, address to, uint256 give, bool adjust) internal virtual {
    741
            // get current balance
    742 0
            (, bytes memory balData) = token.staticcall(abi.encodeWithSelector(0x70a08231, to));
    743 0
            uint256 prevBal = abi.decode(balData, (uint256));
    744
    745
            // update balance
    746 0
            stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(give);
    747
    748
            // update total supply
    749 0
            if (adjust) {
    750 0
                (, bytes memory totSupData) = token.staticcall(abi.encodeWithSelector(0x18160ddd));
    751 0
                uint256 totSup = abi.decode(totSupData, (uint256));
    752 0
                if (give < prevBal) {
    753 0
                    totSup -= (prevBal - give);
    754
                } else {
    755 0
                    totSup += (give - prevBal);
    756
                }
    757 0
                stdstore.target(token).sig(0x18160ddd).checked_write(totSup);
    758
            }
    759
        }
    760
    761
        function dealERC1155(address token, address to, uint256 id, uint256 give, bool adjust) internal virtual {
    762
            // get current balance
    763
            (, bytes memory balData) = token.staticcall(abi.encodeWithSelector(0x00fdd58e, to, id));
    764
            uint256 prevBal = abi.decode(balData, (uint256));
    765
    766
            // update balance
    767
            stdstore.target(token).sig(0x00fdd58e).with_key(to).with_key(id).checked_write(give);
    768
    769
            // update total supply
    770
            if (adjust) {
    771
                (, bytes memory totSupData) = token.staticcall(abi.encodeWithSelector(0xbd85b039, id));
    772
                require(
    773
                    totSupData.length != 0,
    774
                    "StdCheats deal(address,address,uint,uint,bool): target contract is not ERC1155Supply."
    775
                );
    776
                uint256 totSup = abi.decode(totSupData, (uint256));
    777
                if (give < prevBal) {
    778
                    totSup -= (prevBal - give);
    779
                } else {
    780
                    totSup += (give - prevBal);
    781
                }
    782
                stdstore.target(token).sig(0xbd85b039).with_key(id).checked_write(totSup);
    783
            }
    784
        }
    785
    786
        function dealERC721(address token, address to, uint256 id) internal virtual {
    787
            // check if token id is already minted and the actual owner.
    788
            (bool successMinted, bytes memory ownerData) = token.staticcall(abi.encodeWithSelector(0x6352211e, id));
    789
            require(successMinted, "StdCheats deal(address,address,uint,bool): id not minted.");
    790
    791
            // get owner current balance
    792
            (, bytes memory fromBalData) =
    793
                token.staticcall(abi.encodeWithSelector(0x70a08231, abi.decode(ownerData, (address))));
    794
            uint256 fromPrevBal = abi.decode(fromBalData, (uint256));
    795
    796
            // get new user current balance
    797
            (, bytes memory toBalData) = token.staticcall(abi.encodeWithSelector(0x70a08231, to));
    798
            uint256 toPrevBal = abi.decode(toBalData, (uint256));
    799
    800
            // update balances
    801
            stdstore.target(token).sig(0x70a08231).with_key(abi.decode(ownerData, (address))).checked_write(--fromPrevBal);
    802
            stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(++toPrevBal);
    803
    804
            // update owner
    805
            stdstore.target(token).sig(0x6352211e).with_key(id).checked_write(to);
    806
        }
    807
    808
        function deployCodeTo(string memory what, address where) internal virtual {
    809
            deployCodeTo(what, "", 0, where);
    810
        }
    811
    812
        function deployCodeTo(string memory what, bytes memory args, address where) internal virtual {
    813
            deployCodeTo(what, args, 0, where);
    814
        }
    815
    816
        function deployCodeTo(string memory what, bytes memory args, uint256 value, address where) internal virtual {
    817
            bytes memory creationCode = vm.getCode(what);
    818
            vm.etch(where, abi.encodePacked(creationCode, args));
    819
            (bool success, bytes memory runtimeBytecode) = where.call{value: value}("");
    820
            require(success, "StdCheats deployCodeTo(string,bytes,uint256,address): Failed to create runtime bytecode.");
    821
            vm.etch(where, runtimeBytecode);
    822
        }
    823
    824
        // Used to prevent the compilation of console, which shortens the compilation time when console is not used elsewhere.
    825
        function console2_log_StdCheats(string memory p0) private view {
    826
            (bool status,) = address(CONSOLE2_ADDRESS).staticcall(abi.encodeWithSignature("log(string)", p0));
    827
            status;
    828
        }
    829
    }
    17% lib/forge-std/src/StdInvariant.sol
    Lines covered: 4 / 23 (17%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    pragma experimental ABIEncoderV2;
    5
    6
    abstract contract StdInvariant {
    7
        struct FuzzSelector {
    8
            address addr;
    9
            bytes4[] selectors;
    10
        }
    11
    12
        struct FuzzArtifactSelector {
    13
            string artifact;
    14
            bytes4[] selectors;
    15
        }
    16
    17
        struct FuzzInterface {
    18
            address addr;
    19
            string[] artifacts;
    20
        }
    21
    22
        address[] private _excludedContracts;
    23
        address[] private _excludedSenders;
    24
        address[] private _targetedContracts;
    25
        address[] private _targetedSenders;
    26
    27
        string[] private _excludedArtifacts;
    28
        string[] private _targetedArtifacts;
    29
    30
        FuzzArtifactSelector[] private _targetedArtifactSelectors;
    31
    32
        FuzzSelector[] private _excludedSelectors;
    33
        FuzzSelector[] private _targetedSelectors;
    34
    35
        FuzzInterface[] private _targetedInterfaces;
    36
    37
        // Functions for users:
    38
        // These are intended to be called in tests.
    39
    40
        function excludeContract(address newExcludedContract_) internal {
    41
            _excludedContracts.push(newExcludedContract_);
    42
        }
    43
    44
        function excludeSelector(FuzzSelector memory newExcludedSelector_) internal {
    45
            _excludedSelectors.push(newExcludedSelector_);
    46
        }
    47
    48
        function excludeSender(address newExcludedSender_) internal {
    49
            _excludedSenders.push(newExcludedSender_);
    50
        }
    51
    52
        function excludeArtifact(string memory newExcludedArtifact_) internal {
    53
            _excludedArtifacts.push(newExcludedArtifact_);
    54
        }
    55
    56
        function targetArtifact(string memory newTargetedArtifact_) internal {
    57
            _targetedArtifacts.push(newTargetedArtifact_);
    58
        }
    59
    60
        function targetArtifactSelector(FuzzArtifactSelector memory newTargetedArtifactSelector_) internal {
    61
            _targetedArtifactSelectors.push(newTargetedArtifactSelector_);
    62
        }
    63
    64
        function targetContract(address newTargetedContract_) internal {
    65 0
            _targetedContracts.push(newTargetedContract_);
    66
        }
    67
    68
        function targetSelector(FuzzSelector memory newTargetedSelector_) internal {
    69
            _targetedSelectors.push(newTargetedSelector_);
    70
        }
    71
    72 0
        function targetSender(address newTargetedSender_) internal {
    73 0
            _targetedSenders.push(newTargetedSender_);
    74
        }
    75
    76
        function targetInterface(FuzzInterface memory newTargetedInterface_) internal {
    77
            _targetedInterfaces.push(newTargetedInterface_);
    78
        }
    79
    80
        // Functions for forge:
    81
        // These are called by forge to run invariant tests and don't need to be called in tests.
    82
    83 0
        function excludeArtifacts() public view returns (string[] memory excludedArtifacts_) {
    84 0
            excludedArtifacts_ = _excludedArtifacts;
    85
        }
    86
    87 0
        function excludeContracts() public view returns (address[] memory excludedContracts_) {
    88 0
            excludedContracts_ = _excludedContracts;
    89
        }
    90
    91 0
        function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors_) {
    92 0
            excludedSelectors_ = _excludedSelectors;
    93
        }
    94
    95 0
        function excludeSenders() public view returns (address[] memory excludedSenders_) {
    96 0
            excludedSenders_ = _excludedSenders;
    97
        }
    98
    99 0
        function targetArtifacts() public view returns (string[] memory targetedArtifacts_) {
    100 0
            targetedArtifacts_ = _targetedArtifacts;
    101
        }
    102
    103 14×
        function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors_) {
    104 33×
            targetedArtifactSelectors_ = _targetedArtifactSelectors;
    105
        }
    106
    107 0
        function targetContracts() public view returns (address[] memory targetedContracts_) {
    108 0
            targetedContracts_ = _targetedContracts;
    109
        }
    110
    111 0
        function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) {
    112 0
            targetedSelectors_ = _targetedSelectors;
    113
        }
    114
    115 0
        function targetSenders() public view returns (address[] memory targetedSenders_) {
    116 0
            targetedSenders_ = _targetedSenders;
    117
        }
    118
    119
        function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces_) {
    120
            targetedInterfaces_ = _targetedInterfaces;
    121
        }
    122
    }
    100% lib/forge-std/src/Test.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    pragma experimental ABIEncoderV2;
    5
    6
    // 💬 ABOUT
    7
    // Forge Std's default Test.
    8
    9
    // 🧩 MODULES
    10
    import {console} from "./console.sol";
    11
    import {console2} from "./console2.sol";
    12
    import {safeconsole} from "./safeconsole.sol";
    13
    import {StdAssertions} from "./StdAssertions.sol";
    14
    import {StdChains} from "./StdChains.sol";
    15
    import {StdCheats} from "./StdCheats.sol";
    16
    import {StdConstants} from "./StdConstants.sol";
    17
    import {stdError} from "./StdError.sol";
    18
    import {StdInvariant} from "./StdInvariant.sol";
    19
    import {stdJson} from "./StdJson.sol";
    20
    import {stdMath} from "./StdMath.sol";
    21
    import {StdStorage, stdStorage} from "./StdStorage.sol";
    22
    import {StdStyle} from "./StdStyle.sol";
    23
    import {stdToml} from "./StdToml.sol";
    24
    import {StdUtils} from "./StdUtils.sol";
    25
    import {Vm} from "./Vm.sol";
    26
    27
    // 📦 BOILERPLATE
    28
    import {TestBase} from "./Base.sol";
    29
    30
    // ⭐️ TEST
    31
    abstract contract Test is TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils {
    32
        // Note: IS_TEST() must return true.
    33 11×
        bool public IS_TEST = true;
    34
    }
    86% lib/setup-helpers/src/ActorManager.sol
    Lines covered: 13 / 15 (86%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseSetup} from "@chimera/BaseSetup.sol";
    5
    import {vm} from "@chimera/Hevm.sol";
    6
    import {EnumerableSet} from "./EnumerableSet.sol";
    7
    8
    /// @dev This is the source of truth for the actors being used in the test
    9
    /// @notice No actors should be used in the suite without being added here first
    10
    abstract contract ActorManager {
    11
        using EnumerableSet for EnumerableSet.AddressSet;
    12
    13
        ///@notice The current actor being used
    14
        address private _actor;
    15
    16
        ///@notice The list of all actors being used
    17
        EnumerableSet.AddressSet private _actors;
    18
    19
        // If the current target is address(0) then it has not been setup yet and should revert
    20
        error ActorNotSetup();
    21
        // Do not allow duplicates
    22
        error ActorExists();
    23
        // If the actor does not exist
    24
        error ActorNotAdded();
    25
        // Do not allow the default actor
    26
        error DefaultActor();
    27
    28
        /// @notice address(this) is the default actor
    29
        constructor() {
    30
            _actors.add(address(this));
    31
            _actor = address(this);
    32
        }
    33
    34
        /// @notice Returns the current active actor
    35
        function _getActor() internal view returns (address) {
    36
            return _actor;
    37
        }
    38
    39
        /// @notice Returns all actors being used
    40
        function _getActors() internal view returns (address[] memory) {
    41 14×
            return _actors.values();
    42
        }
    43
    44
        /// @notice Adds an actor to the list of actors
    45
        function _addActor(address target) internal {
    46 10×
            if (_actors.contains(target)) {
    47 0
                revert ActorExists();
    48
            }
    49
    50
            if (target == address(this)) {
    51 0
                revert DefaultActor();
    52
            }
    53
    54
            _actors.add(target);
    55
        }
    56
    57
        /// @notice Removes an actor from the list of actors
    58
        function _removeActor(address target) internal {
    59
            if (!_actors.contains(target)) {
    60
                revert ActorNotAdded();
    61
            }
    62
    63
            if (target == address(this)) {
    64
                revert DefaultActor();
    65
            }
    66
    67
            _actors.remove(target);
    68
        }
    69
    70
        /// @dev Expose this in the `TargetFunctions` contract to let the fuzzer switch actors
    71
        ///   NOTE: We revert if the entropy is greater than the number of actors, for Halmos compatibility
    72
        /// @dev This may reduce fuzzing performance if using multiple actors, if so add explicitly clamped handlers to ManagersTargets using the index of all added actors
    73
        /// @notice Switches the current actor based on the entropy
    74
        /// @param entropy The entropy to choose a random actor in the array for switching
    75
        /// @return target The new active actor
    76
        function _switchActor(uint256 entropy) internal returns (address target) {
    77 14×
            target = _actors.at(entropy);
    78 20×
            _actor = target;
    79
        }
    80
    }
    88% lib/setup-helpers/src/AssetManager.sol
    Lines covered: 8 / 9 (88%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseSetup} from "@chimera/BaseSetup.sol";
    5
    import {vm} from "@chimera/Hevm.sol";
    6
    7
    import {EnumerableSet} from "./EnumerableSet.sol";
    8
    import {MockERC20} from "./MockERC20.sol";
    9
    10
    /// @dev Source of truth for the assets being used in the test
    11
    /// @notice No assets should be used in the suite without being added here first
    12
    abstract contract AssetManager {
    13
        using EnumerableSet for EnumerableSet.AddressSet;
    14
    15
        /// @notice The current target for this set of variables
    16
        address private __asset;
    17
    18
        /// @notice The list of all assets being used
    19
        EnumerableSet.AddressSet private _assets;
    20
    21
        // If the current target is address(0) then it has not been setup yet and should revert
    22
        error NotSetup();
    23
        // Do not allow duplicates
    24
        error Exists();
    25
        // Enable only added assets
    26
        error NotAdded();
    27
    28
        /// @notice Returns the current active asset
    29
        function _getAsset() internal view returns (address) {
    30
            if (__asset == address(0)) {
    31
                revert NotSetup();
    32
            }
    33
    34
            return __asset;
    35
        }
    36
    37
        /// @notice Returns all assets being used
    38
        function _getAssets() internal view returns (address[] memory) {
    39
            return _assets.values();
    40
        }
    41
    42
        /// @notice Creates a new asset and adds it to the list of assets
    43
        /// @param decimals The number of decimals for the asset
    44
        /// @return The address of the new asset
    45
        function _newAsset(uint8 decimals) internal returns (address) {
    46
            address asset_ = address(new MockERC20("Test Token", "TST", decimals)); // If names get confusing, concatenate the decimals to the name
    47
            _addAsset(asset_);
    48
            __asset = asset_; // sets the asset as the current asset
    49
            return asset_;
    50
        }
    51
    52
        /// @notice Adds an asset to the list of assets
    53
        /// @param target The address of the asset to add
    54
        function _addAsset(address target) internal {
    55 10×
            if (_assets.contains(target)) {
    56 0
                revert Exists();
    57
            }
    58
    59
            _assets.add(target);
    60
        }
    61
    62
        /// @notice Removes an asset from the list of assets
    63
        /// @param target The address of the asset to remove
    64
        function _removeAsset(address target) internal {
    65
            if (!_assets.contains(target)) {
    66
                revert NotAdded();
    67
            }
    68
    69
            _assets.remove(target);
    70
        }
    71
    72
        /// @notice Switches the current asset based on the entropy
    73
        ///   NOTE: We revert if the entropy is greater than the number of actors, for Halmos compatibility
    74
        /// @param entropy The entropy to choose a random asset in the array for switching
    75
        function _switchAsset(uint256 entropy) internal {
    76 14×
            address target = _assets.at(entropy);
    77 28×
            __asset = target;
    78
        }
    79
    80
        /// === Approve & Mint Asset === ///
    81
    82
        /// @notice Mint initial balance and approve allowances for the active asset
    83
        /// @param actorsArray The array of actors to mint the asset to
    84
        /// @param approvalArray The array of addresses to approve the asset to
    85
        /// @param amount The amount of the asset to mint
    86
        function _finalizeAssetDeployment(address[] memory actorsArray, address[] memory approvalArray, uint256 amount)
    87
            internal
    88
        {
    89
            _mintAssetToAllActors(actorsArray, amount);
    90
            for (uint256 i; i < approvalArray.length; i++) {
    91
                _approveAssetToAddressForAllActors(actorsArray, approvalArray[i]);
    92
            }
    93
        }
    94
    95
        /// @notice Mint the asset to all actors
    96
        /// @param actorsArray The array of actors to mint the asset to
    97
        /// @param amount The amount of the asset to mint
    98
        function _mintAssetToAllActors(address[] memory actorsArray, uint256 amount) private {
    99
            // mint all actors
    100
            address[] memory assets = _getAssets();
    101
            for (uint256 i; i < assets.length; i++) {
    102
                for (uint256 j; j < actorsArray.length; j++) {
    103
                    vm.prank(actorsArray[j]);
    104
                    MockERC20(assets[i]).mint(actorsArray[j], amount);
    105
                }
    106
            }
    107
        }
    108
    109
        /// @notice Approve the asset to all actors
    110
        /// @param actorsArray The array of actors to approve the asset from
    111
        /// @param addressToApprove The address to approve the asset to
    112
        function _approveAssetToAddressForAllActors(address[] memory actorsArray, address addressToApprove) private {
    113
            // approve to all actors
    114
            address[] memory assets = _getAssets();
    115
            for (uint256 i; i < assets.length; i++) {
    116
                for (uint256 j; j < actorsArray.length; j++) {
    117
                    vm.prank(actorsArray[j]);
    118
                    MockERC20(assets[i]).approve(addressToApprove, type(uint256).max);
    119
                }
    120
            }
    121
        }
    122
    }
    90% lib/setup-helpers/src/EnumerableSet.sol
    Lines covered: 19 / 21 (90%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
    4
    5
    pragma solidity ^0.8.0;
    6
    7
    /**
    8
     * @dev Library for managing
    9
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
    10
     * types.
    11
     *
    12
     * Sets have the following properties:
    13
     *
    14
     * - Elements are added, removed, and checked for existence in constant time
    15
     * (O(1)).
    16
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
    17
     *
    18
     * ```solidity
    19
     * contract Example {
    20
     *     // Add the library methods
    21
     *     using EnumerableSet for EnumerableSet.AddressSet;
    22
     *
    23
     *     // Declare a set state variable
    24
     *     EnumerableSet.AddressSet private mySet;
    25
     * }
    26
     * ```
    27
     *
    28
     * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
    29
     * and `uint256` (`UintSet`) are supported.
    30
     *
    31
     * [WARNING]
    32
     * ====
    33
     * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
    34
     * unusable.
    35
     * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
    36
     *
    37
     * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
    38
     * array of EnumerableSet.
    39
     * ====
    40
     */
    41 0
    library EnumerableSet {
    42
        // To implement this library for multiple types with as little code
    43
        // repetition as possible, we write it in terms of a generic Set type with
    44
        // bytes32 values.
    45
        // The Set implementation uses private functions, and user-facing
    46
        // implementations (such as AddressSet) are just wrappers around the
    47
        // underlying Set.
    48
        // This means that we can only create new EnumerableSets for types that fit
    49
        // in bytes32.
    50
    51
        struct Set {
    52
            // Storage of set values
    53
            bytes32[] _values;
    54
            // Position of the value in the `values` array, plus 1 because index 0
    55
            // means a value is not in the set.
    56
            mapping(bytes32 => uint256) _indexes;
    57
        }
    58
    59
        /**
    60
         * @dev Add a value to a set. O(1).
    61
         *
    62
         * Returns true if the value was added to the set, that is if it was not
    63
         * already present.
    64
         */
    65
        function _add(Set storage set, bytes32 value) private returns (bool) {
    66
            if (!_contains(set, value)) {
    67 22×
                set._values.push(value);
    68
                // The value is stored at length-1, but we add 1 to all indexes
    69
                // and use 0 as a sentinel value
    70 18×
                set._indexes[value] = set._values.length;
    71
                return true;
    72
            } else {
    73 0
                return false;
    74
            }
    75
        }
    76
    77
        /**
    78
         * @dev Removes a value from a set. O(1).
    79
         *
    80
         * Returns true if the value was removed from the set, that is if it was
    81
         * present.
    82
         */
    83
        function _remove(Set storage set, bytes32 value) private returns (bool) {
    84
            // We read and store the value's index to prevent multiple reads from the same storage slot
    85
            uint256 valueIndex = set._indexes[value];
    86
    87
            if (valueIndex != 0) {
    88
                // Equivalent to contains(set, value)
    89
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    90
                // the array, and then remove the last element (sometimes called as 'swap and pop').
    91
                // This modifies the order of the array, as noted in {at}.
    92
    93
                uint256 toDeleteIndex = valueIndex - 1;
    94
                uint256 lastIndex = set._values.length - 1;
    95
    96
                if (lastIndex != toDeleteIndex) {
    97
                    bytes32 lastValue = set._values[lastIndex];
    98
    99
                    // Move the last value to the index where the value to delete is
    100
                    set._values[toDeleteIndex] = lastValue;
    101
                    // Update the index for the moved value
    102
                    set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
    103
                }
    104
    105
                // Delete the slot where the moved value was stored
    106
                set._values.pop();
    107
    108
                // Delete the index for the deleted slot
    109
                delete set._indexes[value];
    110
    111
                return true;
    112
            } else {
    113
                return false;
    114
            }
    115
        }
    116
    117
        /**
    118
         * @dev Returns true if the value is in the set. O(1).
    119
         */
    120
        function _contains(Set storage set, bytes32 value) private view returns (bool) {
    121 26×
            return set._indexes[value] != 0;
    122
        }
    123
    124
        /**
    125
         * @dev Returns the number of values on the set. O(1).
    126
         */
    127
        function _length(Set storage set) private view returns (uint256) {
    128
            return set._values.length;
    129
        }
    130
    131
        /**
    132
         * @dev Returns the value stored at position `index` in the set. O(1).
    133
         *
    134
         * Note that there are no guarantees on the ordering of values inside the
    135
         * array, and it may change when more values are added or removed.
    136
         *
    137
         * Requirements:
    138
         *
    139
         * - `index` must be strictly less than {length}.
    140
         */
    141 14×
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
    142 42×
            return set._values[index];
    143
        }
    144
    145
        /**
    146
         * @dev Return the entire set in an array
    147
         *
    148
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    149
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    150
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    151
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    152
         */
    153 12×
        function _values(Set storage set) private view returns (bytes32[] memory) {
    154 138×
            return set._values;
    155
        }
    156
    157
        // Bytes32Set
    158
    159
        struct Bytes32Set {
    160
            Set _inner;
    161
        }
    162
    163
        /**
    164
         * @dev Add a value to a set. O(1).
    165
         *
    166
         * Returns true if the value was added to the set, that is if it was not
    167
         * already present.
    168
         */
    169
        function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    170
            return _add(set._inner, value);
    171
        }
    172
    173
        /**
    174
         * @dev Removes a value from a set. O(1).
    175
         *
    176
         * Returns true if the value was removed from the set, that is if it was
    177
         * present.
    178
         */
    179
        function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    180
            return _remove(set._inner, value);
    181
        }
    182
    183
        /**
    184
         * @dev Returns true if the value is in the set. O(1).
    185
         */
    186
        function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
    187
            return _contains(set._inner, value);
    188
        }
    189
    190
        /**
    191
         * @dev Returns the number of values in the set. O(1).
    192
         */
    193
        function length(Bytes32Set storage set) internal view returns (uint256) {
    194
            return _length(set._inner);
    195
        }
    196
    197
        /**
    198
         * @dev Returns the value stored at position `index` in the set. O(1).
    199
         *
    200
         * Note that there are no guarantees on the ordering of values inside the
    201
         * array, and it may change when more values are added or removed.
    202
         *
    203
         * Requirements:
    204
         *
    205
         * - `index` must be strictly less than {length}.
    206
         */
    207
        function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
    208
            return _at(set._inner, index);
    209
        }
    210
    211
        /**
    212
         * @dev Return the entire set in an array
    213
         *
    214
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    215
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    216
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    217
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    218
         */
    219
        function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
    220
            bytes32[] memory store = _values(set._inner);
    221
            bytes32[] memory result;
    222
    223
            /// @solidity memory-safe-assembly
    224
            assembly {
    225
                result := store
    226
            }
    227
    228
            return result;
    229
        }
    230
    231
        // AddressSet
    232
    233
        struct AddressSet {
    234
            Set _inner;
    235
        }
    236
    237
        /**
    238
         * @dev Add a value to a set. O(1).
    239
         *
    240
         * Returns true if the value was added to the set, that is if it was not
    241
         * already present.
    242
         */
    243
        function add(AddressSet storage set, address value) internal returns (bool) {
    244
            return _add(set._inner, bytes32(uint256(uint160(value))));
    245
        }
    246
    247
        /**
    248
         * @dev Removes a value from a set. O(1).
    249
         *
    250
         * Returns true if the value was removed from the set, that is if it was
    251
         * present.
    252
         */
    253
        function remove(AddressSet storage set, address value) internal returns (bool) {
    254
            return _remove(set._inner, bytes32(uint256(uint160(value))));
    255
        }
    256
    257
        /**
    258
         * @dev Returns true if the value is in the set. O(1).
    259
         */
    260
        function contains(AddressSet storage set, address value) internal view returns (bool) {
    261
            return _contains(set._inner, bytes32(uint256(uint160(value))));
    262
        }
    263
    264
        /**
    265
         * @dev Returns the number of values in the set. O(1).
    266
         */
    267
        function length(AddressSet storage set) internal view returns (uint256) {
    268
            return _length(set._inner);
    269
        }
    270
    271
        /**
    272
         * @dev Returns the value stored at position `index` in the set. O(1).
    273
         *
    274
         * Note that there are no guarantees on the ordering of values inside the
    275
         * array, and it may change when more values are added or removed.
    276
         *
    277
         * Requirements:
    278
         *
    279
         * - `index` must be strictly less than {length}.
    280
         */
    281
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
    282 10×
            return address(uint160(uint256(_at(set._inner, index))));
    283
        }
    284
    285
        /**
    286
         * @dev Return the entire set in an array
    287
         *
    288
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    289
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    290
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    291
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    292
         */
    293
        function values(AddressSet storage set) internal view returns (address[] memory) {
    294 10×
            bytes32[] memory store = _values(set._inner);
    295
            address[] memory result;
    296
    297
            /// @solidity memory-safe-assembly
    298
            assembly {
    299
                result := store
    300
            }
    301
    302
            return result;
    303
        }
    304
    305
        // UintSet
    306
    307
        struct UintSet {
    308
            Set _inner;
    309
        }
    310
    311
        /**
    312
         * @dev Add a value to a set. O(1).
    313
         *
    314
         * Returns true if the value was added to the set, that is if it was not
    315
         * already present.
    316
         */
    317
        function add(UintSet storage set, uint256 value) internal returns (bool) {
    318
            return _add(set._inner, bytes32(value));
    319
        }
    320
    321
        /**
    322
         * @dev Removes a value from a set. O(1).
    323
         *
    324
         * Returns true if the value was removed from the set, that is if it was
    325
         * present.
    326
         */
    327
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
    328
            return _remove(set._inner, bytes32(value));
    329
        }
    330
    331
        /**
    332
         * @dev Returns true if the value is in the set. O(1).
    333
         */
    334
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
    335
            return _contains(set._inner, bytes32(value));
    336
        }
    337
    338
        /**
    339
         * @dev Returns the number of values in the set. O(1).
    340
         */
    341
        function length(UintSet storage set) internal view returns (uint256) {
    342
            return _length(set._inner);
    343
        }
    344
    345
        /**
    346
         * @dev Returns the value stored at position `index` in the set. O(1).
    347
         *
    348
         * Note that there are no guarantees on the ordering of values inside the
    349
         * array, and it may change when more values are added or removed.
    350
         *
    351
         * Requirements:
    352
         *
    353
         * - `index` must be strictly less than {length}.
    354
         */
    355
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
    356
            return uint256(_at(set._inner, index));
    357
        }
    358
    359
        /**
    360
         * @dev Return the entire set in an array
    361
         *
    362
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    363
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    364
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    365
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    366
         */
    367
        function values(UintSet storage set) internal view returns (uint256[] memory) {
    368
            bytes32[] memory store = _values(set._inner);
    369
            uint256[] memory result;
    370
    371
            /// @solidity memory-safe-assembly
    372
            assembly {
    373
                result := store
    374
            }
    375
    376
            return result;
    377
        }
    378
    }
    22% src/access/AccessManagerEnumerable.sol
    Lines covered: 8 / 36 (22%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {AccessManager} from 'src/dependencies/openzeppelin/AccessManager.sol';
    6
    import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol';
    7
    import {IAccessManagerEnumerable} from 'src/access/interfaces/IAccessManagerEnumerable.sol';
    8
    9
    /// @title AccessManagerEnumerable
    10
    /// @author Aave Labs
    11
    /// @notice Extension of AccessManager that tracks role members and their function selectors using EnumerableSet.
    12 115×
    contract AccessManagerEnumerable is AccessManager, IAccessManagerEnumerable {
    13
      using EnumerableSet for EnumerableSet.AddressSet;
    14
      using EnumerableSet for EnumerableSet.Bytes32Set;
    15
    16
      /// @dev Map of role identifiers to their respective member sets.
    17
      mapping(uint64 roleId => EnumerableSet.AddressSet) private _roleMembers;
    18
    19
      /// @dev Map of role identifiers and target contract addresses to their respective set of function selectors.
    20
      mapping(uint64 roleId => mapping(address target => EnumerableSet.Bytes32Set))
    21
        private _roleTargetFunctions;
    22
    23 29×
      constructor(address initialAdmin_) AccessManager(initialAdmin_) {}
    24
    25
      /// @inheritdoc IAccessManagerEnumerable
    26 0
      function getRoleMember(uint64 roleId, uint256 index) external view returns (address) {
    27 0
        return _roleMembers[roleId].at(index);
    28
      }
    29
    30
      /// @inheritdoc IAccessManagerEnumerable
    31 0
      function getRoleMemberCount(uint64 roleId) external view returns (uint256) {
    32 0
        return _roleMembers[roleId].length();
    33
      }
    34
    35
      /// @inheritdoc IAccessManagerEnumerable
    36 0
      function getRoleMembers(
    37
        uint64 roleId,
    38
        uint256 start,
    39
        uint256 end
    40 0
      ) external view returns (address[] memory) {
    41 0
        return _roleMembers[roleId].values(start, end);
    42
      }
    43
    44
      /// @inheritdoc IAccessManagerEnumerable
    45 0
      function getRoleTargetFunction(
    46
        uint64 roleId,
    47
        address target,
    48
        uint256 index
    49 0
      ) external view returns (bytes4) {
    50 0
        return bytes4(_roleTargetFunctions[roleId][target].at(index));
    51
      }
    52
    53
      /// @inheritdoc IAccessManagerEnumerable
    54 0
      function getRoleTargetFunctionCount(
    55
        uint64 roleId,
    56
        address target
    57 0
      ) external view returns (uint256) {
    58 0
        return _roleTargetFunctions[roleId][target].length();
    59
      }
    60
    61
      /// @inheritdoc IAccessManagerEnumerable
    62 0
      function getRoleTargetFunctions(
    63
        uint64 roleId,
    64
        address target,
    65
        uint256 start,
    66
        uint256 end
    67 0
      ) external view returns (bytes4[] memory) {
    68 0
        bytes32[] memory targetFunctions = _roleTargetFunctions[roleId][target].values(start, end);
    69
        bytes4[] memory targetFunctionSelectors;
    70
        assembly ('memory-safe') {
    71
          targetFunctionSelectors := targetFunctions
    72
        }
    73
        return targetFunctionSelectors;
    74
      }
    75
    76
      /// @dev Override AccessManager `_grantRole` function to track role members.
    77
      function _grantRole(
    78
        uint64 roleId,
    79
        address account,
    80
        uint32 grantDelay,
    81
        uint32 executionDelay
    82
      ) internal override returns (bool) {
    83 10×
        bool granted = super._grantRole(roleId, account, grantDelay, executionDelay);
    84
        if (granted) {
    85 19×
          _roleMembers[roleId].add(account);
    86
        }
    87
        return granted;
    88
      }
    89
    90
      /// @dev Override AccessManager `_revokeRole` function to remove from tracked role members.
    91 0
      function _revokeRole(uint64 roleId, address account) internal override returns (bool) {
    92 0
        bool revoked = super._revokeRole(roleId, account);
    93 0
        if (revoked) {
    94 0
          _roleMembers[roleId].remove(account);
    95
        }
    96 0
        return revoked;
    97
      }
    98
    99
      /// @dev Override AccessManager `_setTargetFunctionRole` function to track function selectors attributed to roles.
    100 0
      function _setTargetFunctionRole(
    101
        address target,
    102
        bytes4 selector,
    103
        uint64 roleId
    104
      ) internal override {
    105 0
        uint64 oldRoleId = getTargetFunctionRole(target, selector);
    106 0
        super._setTargetFunctionRole(target, selector, roleId);
    107 0
        if (oldRoleId != ADMIN_ROLE) {
    108 0
          _roleTargetFunctions[oldRoleId][target].remove(bytes32(selector));
    109
        }
    110 0
        if (roleId != ADMIN_ROLE) {
    111 0
          _roleTargetFunctions[roleId][target].add(bytes32(selector));
    112
        }
    113
      }
    114
    }
    67% src/dependencies/openzeppelin/AccessManaged.sol
    Lines covered: 19 / 28 (67%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (access/manager/AccessManaged.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {AuthorityUtils} from './AuthorityUtils.sol';
    7
    import {IAccessManager} from './IAccessManager.sol';
    8
    import {IAccessManaged} from './IAccessManaged.sol';
    9
    import {Context} from './Context.sol';
    10
    11
    /**
    12
     * @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be
    13
     * permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface,
    14
     * implementing a policy that allows certain callers to access certain functions.
    15
     *
    16
     * IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public`
    17
     * functions, and ideally only used in `external` functions. See {restricted}.
    18
     */
    19
    abstract contract AccessManaged is Context, IAccessManaged {
    20
      address private _authority;
    21
    22
      bool private _consumingSchedule;
    23
    24
      /**
    25
       * @dev Initializes the contract connected to an initial authority.
    26
       */
    27
      constructor(address initialAuthority) {
    28
        _setAuthority(initialAuthority);
    29
      }
    30
    31
      /**
    32
       * @dev Restricts access to a function as defined by the connected Authority for this contract and the
    33
       * caller and selector of the function that entered the contract.
    34
       *
    35
       * [IMPORTANT]
    36
       * ====
    37
       * In general, this modifier should only be used on `external` functions. It is okay to use it on `public`
    38
       * functions that are used as external entry points and are not called internally. Unless you know what you're
    39
       * doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security
    40
       * implications! This is because the permissions are determined by the function that entered the contract, i.e. the
    41
       * function at the bottom of the call stack, and not the function where the modifier is visible in the source code.
    42
       * ====
    43
       *
    44
       * [WARNING]
    45
       * ====
    46
       * Avoid adding this modifier to the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`]
    47
       * function or the https://docs.soliditylang.org/en/v0.8.20/contracts.html#fallback-function[`fallback()`]. These
    48
       * functions are the only execution paths where a function selector cannot be unambiguously determined from the calldata
    49
       * since the selector defaults to `0x00000000` in the `receive()` function and similarly in the `fallback()` function
    50
       * if no calldata is provided. (See {_checkCanCall}).
    51
       *
    52
       * The `receive()` function will always panic whereas the `fallback()` may panic depending on the calldata length.
    53
       * ====
    54
       */
    55
      modifier restricted() {
    56 19×
        _checkCanCall(_msgSender(), _msgData());
    57
        _;
    58
      }
    59
    60
      /// @inheritdoc IAccessManaged
    61
      function authority() public view virtual returns (address) {
    62
        return _authority;
    63
      }
    64
    65
      /// @inheritdoc IAccessManaged
    66
      function setAuthority(address newAuthority) public virtual {
    67
        address caller = _msgSender();
    68 0
        if (caller != authority()) {
    69 0
          revert AccessManagedUnauthorized(caller);
    70
        }
    71 0
        if (newAuthority.code.length == 0) {
    72 0
          revert AccessManagedInvalidAuthority(newAuthority);
    73
        }
    74
        _setAuthority(newAuthority);
    75
      }
    76
    77
      /// @inheritdoc IAccessManaged
    78 0
      function isConsumingScheduledOp() public view returns (bytes4) {
    79 0
        return _consumingSchedule ? this.isConsumingScheduledOp.selector : bytes4(0);
    80
      }
    81
    82
      /**
    83
       * @dev Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the
    84
       * permissions set by the current authority.
    85
       */
    86
      function _setAuthority(address newAuthority) internal virtual {
    87 12×
        _authority = newAuthority;
    88 11×
        emit AuthorityUpdated(newAuthority);
    89
      }
    90
    91
      /**
    92
       * @dev Reverts if the caller is not allowed to call the function identified by a selector. Panics if the calldata
    93
       * is less than 4 bytes long.
    94
       */
    95 20×
      function _checkCanCall(address caller, bytes calldata data) internal virtual {
    96 20×
        (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay(
    97
          authority(),
    98
          caller,
    99
          address(this),
    100 26×
          bytes4(data[0:4])
    101
        );
    102
        if (!immediate) {
    103 10×
          if (delay > 0) {
    104 0
            _consumingSchedule = true;
    105 0
            IAccessManager(authority()).consumeScheduledOp(caller, data);
    106 0
            _consumingSchedule = false;
    107
          } else {
    108 12×
            revert AccessManagedUnauthorized(caller);
    109
          }
    110
        }
    111
      }
    112
    }
    14% src/dependencies/openzeppelin/AccessManager.sol
    Lines covered: 36 / 250 (14%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManager.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {IAccessManager} from './IAccessManager.sol';
    7
    import {IAccessManaged} from './IAccessManaged.sol';
    8
    import {Address} from './Address.sol';
    9
    import {Context} from './Context.sol';
    10
    import {Multicall} from './Multicall.sol';
    11
    import {Math} from './Math.sol';
    12
    import {Time} from './Time.sol';
    13
    14
    /**
    15
     * @dev AccessManager is a central contract to store the permissions of a system.
    16
     *
    17
     * A smart contract under the control of an AccessManager instance is known as a target, and will inherit from the
    18
     * {AccessManaged} contract, be connected to this contract as its manager and implement the {AccessManaged-restricted}
    19
     * modifier on a set of functions selected to be permissioned. Note that any function without this setup won't be
    20
     * effectively restricted.
    21
     *
    22
     * The restriction rules for such functions are defined in terms of "roles" identified by an `uint64` and scoped
    23
     * by target (`address`) and function selectors (`bytes4`). These roles are stored in this contract and can be
    24
     * configured by admins (`ADMIN_ROLE` members) after a delay (see {getTargetAdminDelay}).
    25
     *
    26
     * For each target contract, admins can configure the following without any delay:
    27
     *
    28
     * * The target's {AccessManaged-authority} via {updateAuthority}.
    29
     * * Close or open a target via {setTargetClosed} keeping the permissions intact.
    30
     * * The roles that are allowed (or disallowed) to call a given function (identified by its selector) through {setTargetFunctionRole}.
    31
     *
    32
     * By default every address is member of the `PUBLIC_ROLE` and every target function is restricted to the `ADMIN_ROLE` until configured otherwise.
    33
     * Additionally, each role has the following configuration options restricted to this manager's admins:
    34
     *
    35
     * * A role's admin role via {setRoleAdmin} who can grant or revoke roles.
    36
     * * A role's guardian role via {setRoleGuardian} who's allowed to cancel operations.
    37
     * * A delay in which a role takes effect after being granted through {setGrantDelay}.
    38
     * * A delay of any target's admin action via {setTargetAdminDelay}.
    39
     * * A role label for discoverability purposes with {labelRole}.
    40
     *
    41
     * Any account can be added and removed into any number of these roles by using the {grantRole} and {revokeRole} functions
    42
     * restricted to each role's admin (see {getRoleAdmin}).
    43
     *
    44
     * Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that
    45
     * they will be highly secured (e.g., a multisig or a well-configured DAO).
    46
     *
    47
     * NOTE: This contract implements a form of the {IAuthority} interface, but {canCall} has additional return data so it
    48
     * doesn't inherit `IAuthority`. It is however compatible with the `IAuthority` interface since the first 32 bytes of
    49
     * the return data are a boolean as expected by that interface.
    50
     *
    51
     * NOTE: Systems that implement other access control mechanisms (for example using {Ownable}) can be paired with an
    52
     * {AccessManager} by transferring permissions (ownership in the case of {Ownable}) directly to the {AccessManager}.
    53
     * Users will be able to interact with these contracts through the {execute} function, following the access rules
    54
     * registered in the {AccessManager}. Keep in mind that in that context, the msg.sender seen by restricted functions
    55
     * will be {AccessManager} itself.
    56
     *
    57
     * WARNING: When granting permissions over an {Ownable} or {AccessControl} contract to an {AccessManager}, be very
    58
     * mindful of the danger associated with functions such as {Ownable-renounceOwnership} or
    59
     * {AccessControl-renounceRole}.
    60
     */
    61 0
    contract AccessManager is Context, Multicall, IAccessManager {
    62
      using Time for *;
    63
    64
      // Structure that stores the details for a target contract.
    65
      struct TargetConfig {
    66
        mapping(bytes4 selector => uint64 roleId) allowedRoles;
    67
        Time.Delay adminDelay;
    68
        bool closed;
    69
      }
    70
    71
      // Structure that stores the details for a role/account pair. This structures fit into a single slot.
    72
      struct Access {
    73
        // Timepoint at which the user gets the permission.
    74
        // If this is either 0 or in the future, then the role permission is not available.
    75
        uint48 since;
    76
        // Delay for execution. Only applies to restricted() / execute() calls.
    77
        Time.Delay delay;
    78
      }
    79
    80
      // Structure that stores the details of a role.
    81
      struct Role {
    82
        // Members of the role.
    83
        mapping(address user => Access access) members;
    84
        // Admin who can grant or revoke permissions.
    85
        uint64 admin;
    86
        // Guardian who can cancel operations targeting functions that need this role.
    87
        uint64 guardian;
    88
        // Delay in which the role takes effect after being granted.
    89
        Time.Delay grantDelay;
    90
      }
    91
    92
      // Structure that stores the details for a scheduled operation. This structure fits into a single slot.
    93
      struct Schedule {
    94
        // Moment at which the operation can be executed.
    95
        uint48 timepoint;
    96
        // Operation nonce to allow third-party contracts to identify the operation.
    97
        uint32 nonce;
    98
      }
    99
    100
      /**
    101
       * @dev The identifier of the admin role. Required to perform most configuration operations including
    102
       * other roles' management and target restrictions.
    103
       */
    104
      uint64 public constant ADMIN_ROLE = type(uint64).min; // 0
    105
    106
      /**
    107
       * @dev The identifier of the public role. Automatically granted to all addresses with no delay.
    108
       */
    109 0
      uint64 public constant PUBLIC_ROLE = type(uint64).max; // 2**64-1
    110
    111
      mapping(address target => TargetConfig mode) private _targets;
    112
      mapping(uint64 roleId => Role) private _roles;
    113
      mapping(bytes32 operationId => Schedule) private _schedules;
    114
    115
      // Used to identify operations that are currently being executed via {execute}.
    116
      // This should be transient storage when supported by the EVM.
    117
      bytes32 private _executionId;
    118
    119
      /**
    120
       * @dev Check that the caller is authorized to perform the operation.
    121
       * See {AccessManager} description for a detailed breakdown of the authorization logic.
    122
       */
    123
      modifier onlyAuthorized() {
    124 0
        _checkAuthorized();
    125
        _;
    126
      }
    127
    128
      constructor(address initialAdmin) {
    129
        if (initialAdmin == address(0)) {
    130 0
          revert AccessManagerInvalidInitialAdmin(address(0));
    131
        }
    132
    133
        // admin is active immediately and without any execution delay.
    134
        _grantRole(ADMIN_ROLE, initialAdmin, 0, 0);
    135
      }
    136
    137
      // =================================================== GETTERS ====================================================
    138
      /// @inheritdoc IAccessManager
    139 58×
      function canCall(
    140
        address caller,
    141
        address target,
    142
        bytes4 selector
    143
      ) public view virtual returns (bool immediate, uint32 delay) {
    144 18×
        if (isTargetClosed(target)) {
    145 0
          return (false, 0);
    146 16×
        } else if (caller == address(this)) {
    147
          // Caller is AccessManager, this means the call was sent through {execute} and it already checked
    148
          // permissions. We verify that the call "identifier", which is set during {execute}, is correct.
    149 0
          return (_isExecuting(target, selector), 0);
    150
        } else {
    151 18×
          uint64 roleId = getTargetFunctionRole(target, selector);
    152 24×
          (bool isMember, uint32 currentDelay) = hasRole(roleId, caller);
    153 32×
          return isMember ? (currentDelay == 0, currentDelay) : (false, 0);
    154
        }
    155
      }
    156
    157
      /// @inheritdoc IAccessManager
    158 0
      function expiration() public view virtual returns (uint32) {
    159 0
        return 1 weeks;
    160
      }
    161
    162
      /// @inheritdoc IAccessManager
    163 0
      function minSetback() public view virtual returns (uint32) {
    164 0
        return 5 days;
    165
      }
    166
    167
      /// @inheritdoc IAccessManager
    168
      function isTargetClosed(address target) public view virtual returns (bool) {
    169 38×
        return _targets[target].closed;
    170
      }
    171
    172
      /// @inheritdoc IAccessManager
    173 12×
      function getTargetFunctionRole(
    174
        address target,
    175
        bytes4 selector
    176
      ) public view virtual returns (uint64) {
    177 48×
        return _targets[target].allowedRoles[selector];
    178
      }
    179
    180
      /// @inheritdoc IAccessManager
    181 0
      function getTargetAdminDelay(address target) public view virtual returns (uint32) {
    182 0
        return _targets[target].adminDelay.get();
    183
      }
    184
    185
      /// @inheritdoc IAccessManager
    186 0
      function getRoleAdmin(uint64 roleId) public view virtual returns (uint64) {
    187 0
        return _roles[roleId].admin;
    188
      }
    189
    190
      /// @inheritdoc IAccessManager
    191 16×
      function getRoleGuardian(uint64 roleId) public view virtual returns (uint64) {
    192 0
        return _roles[roleId].guardian;
    193
      }
    194
    195
      /// @inheritdoc IAccessManager
    196 0
      function getRoleGrantDelay(uint64 roleId) public view virtual returns (uint32) {
    197 0
        return _roles[roleId].grantDelay.get();
    198
      }
    199
    200
      /// @inheritdoc IAccessManager
    201 10×
      function getAccess(
    202
        uint64 roleId,
    203
        address account
    204
      )
    205
        public
    206
        view
    207
        virtual
    208 12×
        returns (uint48 since, uint32 currentDelay, uint32 pendingDelay, uint48 effect)
    209
      {
    210 48×
        Access storage access = _roles[roleId].members[account];
    211
    212 12×
        since = access.since;
    213 22×
        (currentDelay, pendingDelay, effect) = access.delay.getFull();
    214
    215
        return (since, currentDelay, pendingDelay, effect);
    216
      }
    217
    218
      /// @inheritdoc IAccessManager
    219 14×
      function hasRole(
    220
        uint64 roleId,
    221
        address account
    222
      ) public view virtual returns (bool isMember, uint32 executionDelay) {
    223 14×
        if (roleId == PUBLIC_ROLE) {
    224 0
          return (true, 0);
    225
        } else {
    226 28×
          (uint48 hasRoleSince, uint32 currentDelay, , ) = getAccess(roleId, account);
    227 44×
          return (hasRoleSince != 0 && hasRoleSince <= Time.timestamp(), currentDelay);
    228
        }
    229
      }
    230
    231
      // =============================================== ROLE MANAGEMENT ===============================================
    232
      /// @inheritdoc IAccessManager
    233 0
      function labelRole(uint64 roleId, string calldata label) public virtual onlyAuthorized {
    234 0
        if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) {
    235 0
          revert AccessManagerLockedRole(roleId);
    236
        }
    237 0
        emit RoleLabel(roleId, label);
    238
      }
    239
    240
      /// @inheritdoc IAccessManager
    241 0
      function grantRole(
    242
        uint64 roleId,
    243
        address account,
    244
        uint32 executionDelay
    245
      ) public virtual onlyAuthorized {
    246 0
        _grantRole(roleId, account, getRoleGrantDelay(roleId), executionDelay);
    247
      }
    248
    249
      /// @inheritdoc IAccessManager
    250 0
      function revokeRole(uint64 roleId, address account) public virtual onlyAuthorized {
    251 0
        _revokeRole(roleId, account);
    252
      }
    253
    254
      /// @inheritdoc IAccessManager
    255 0
      function renounceRole(uint64 roleId, address callerConfirmation) public virtual {
    256 0
        if (callerConfirmation != _msgSender()) {
    257 0
          revert AccessManagerBadConfirmation();
    258
        }
    259
        _revokeRole(roleId, callerConfirmation);
    260
      }
    261
    262
      /// @inheritdoc IAccessManager
    263 0
      function setRoleAdmin(uint64 roleId, uint64 admin) public virtual onlyAuthorized {
    264 0
        _setRoleAdmin(roleId, admin);
    265
      }
    266
    267
      /// @inheritdoc IAccessManager
    268 0
      function setRoleGuardian(uint64 roleId, uint64 guardian) public virtual onlyAuthorized {
    269 0
        _setRoleGuardian(roleId, guardian);
    270
      }
    271
    272
      /// @inheritdoc IAccessManager
    273 0
      function setGrantDelay(uint64 roleId, uint32 newDelay) public virtual onlyAuthorized {
    274 0
        _setGrantDelay(roleId, newDelay);
    275
      }
    276
    277
      /**
    278
       * @dev Internal version of {grantRole} without access control. Returns true if the role was newly granted.
    279
       *
    280
       * Emits a {RoleGranted} event.
    281
       */
    282
      function _grantRole(
    283
        uint64 roleId,
    284
        address account,
    285
        uint32 grantDelay,
    286
        uint32 executionDelay
    287
      ) internal virtual returns (bool) {
    288
        if (roleId == PUBLIC_ROLE) {
    289 0
          revert AccessManagerLockedRole(roleId);
    290
        }
    291
    292 29×
        bool newMember = _roles[roleId].members[account].since == 0;
    293
        uint48 since;
    294
    295
        if (newMember) {
    296 15×
          since = Time.timestamp() + grantDelay;
    297 84×
          _roles[roleId].members[account] = Access({since: since, delay: executionDelay.toDelay()});
    298
        } else {
    299
          // No setback here. Value can be reset by doing revoke + grant, effectively allowing the admin to perform
    300
          // any change to the execution delay within the duration of the role admin delay.
    301 0
          (_roles[roleId].members[account].delay, since) = _roles[roleId]
    302
            .members[account]
    303
            .delay
    304 0
            .withUpdate(executionDelay, 0);
    305
        }
    306
    307 19×
        emit RoleGranted(roleId, account, executionDelay, since, newMember);
    308
        return newMember;
    309
      }
    310
    311
      /**
    312
       * @dev Internal version of {revokeRole} without access control. This logic is also used by {renounceRole}.
    313
       * Returns true if the role was previously granted.
    314
       *
    315
       * Emits a {RoleRevoked} event if the account had the role.
    316
       */
    317 0
      function _revokeRole(uint64 roleId, address account) internal virtual returns (bool) {
    318 0
        if (roleId == PUBLIC_ROLE) {
    319 0
          revert AccessManagerLockedRole(roleId);
    320
        }
    321
    322 0
        if (_roles[roleId].members[account].since == 0) {
    323 0
          return false;
    324
        }
    325
    326 0
        delete _roles[roleId].members[account];
    327
    328 0
        emit RoleRevoked(roleId, account);
    329 0
        return true;
    330
      }
    331
    332
      /**
    333
       * @dev Internal version of {setRoleAdmin} without access control.
    334
       *
    335
       * Emits a {RoleAdminChanged} event.
    336
       *
    337
       * NOTE: Setting the admin role as the `PUBLIC_ROLE` is allowed, but it will effectively allow
    338
       * anyone to set grant or revoke such role.
    339
       */
    340 0
      function _setRoleAdmin(uint64 roleId, uint64 admin) internal virtual {
    341 0
        if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) {
    342 0
          revert AccessManagerLockedRole(roleId);
    343
        }
    344
    345 0
        _roles[roleId].admin = admin;
    346
    347 0
        emit RoleAdminChanged(roleId, admin);
    348
      }
    349
    350
      /**
    351
       * @dev Internal version of {setRoleGuardian} without access control.
    352
       *
    353
       * Emits a {RoleGuardianChanged} event.
    354
       *
    355
       * NOTE: Setting the guardian role as the `PUBLIC_ROLE` is allowed, but it will effectively allow
    356
       * anyone to cancel any scheduled operation for such role.
    357
       */
    358 0
      function _setRoleGuardian(uint64 roleId, uint64 guardian) internal virtual {
    359 0
        if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) {
    360 0
          revert AccessManagerLockedRole(roleId);
    361
        }
    362
    363 0
        _roles[roleId].guardian = guardian;
    364
    365 0
        emit RoleGuardianChanged(roleId, guardian);
    366
      }
    367
    368
      /**
    369
       * @dev Internal version of {setGrantDelay} without access control.
    370
       *
    371
       * Emits a {RoleGrantDelayChanged} event.
    372
       */
    373 0
      function _setGrantDelay(uint64 roleId, uint32 newDelay) internal virtual {
    374 0
        if (roleId == PUBLIC_ROLE) {
    375 0
          revert AccessManagerLockedRole(roleId);
    376
        }
    377
    378 0
        uint48 effect;
    379 0
        (_roles[roleId].grantDelay, effect) = _roles[roleId].grantDelay.withUpdate(
    380 0
          newDelay,
    381
          minSetback()
    382
        );
    383
    384 0
        emit RoleGrantDelayChanged(roleId, newDelay, effect);
    385
      }
    386
    387
      // ============================================= FUNCTION MANAGEMENT ==============================================
    388
      /// @inheritdoc IAccessManager
    389 0
      function setTargetFunctionRole(
    390
        address target,
    391
        bytes4[] calldata selectors,
    392
        uint64 roleId
    393
      ) public virtual onlyAuthorized {
    394 0
        for (uint256 i = 0; i < selectors.length; ++i) {
    395 0
          _setTargetFunctionRole(target, selectors[i], roleId);
    396
        }
    397
      }
    398
    399
      /**
    400
       * @dev Internal version of {setTargetFunctionRole} without access control.
    401
       *
    402
       * Emits a {TargetFunctionRoleUpdated} event.
    403
       */
    404 0
      function _setTargetFunctionRole(address target, bytes4 selector, uint64 roleId) internal virtual {
    405 0
        _targets[target].allowedRoles[selector] = roleId;
    406 0
        emit TargetFunctionRoleUpdated(target, selector, roleId);
    407
      }
    408
    409
      /// @inheritdoc IAccessManager
    410 0
      function setTargetAdminDelay(address target, uint32 newDelay) public virtual onlyAuthorized {
    411 0
        _setTargetAdminDelay(target, newDelay);
    412
      }
    413
    414
      /**
    415
       * @dev Internal version of {setTargetAdminDelay} without access control.
    416
       *
    417
       * Emits a {TargetAdminDelayUpdated} event.
    418
       */
    419 0
      function _setTargetAdminDelay(address target, uint32 newDelay) internal virtual {
    420 0
        uint48 effect;
    421 0
        (_targets[target].adminDelay, effect) = _targets[target].adminDelay.withUpdate(
    422 0
          newDelay,
    423
          minSetback()
    424
        );
    425
    426 0
        emit TargetAdminDelayUpdated(target, newDelay, effect);
    427
      }
    428
    429
      // =============================================== MODE MANAGEMENT ================================================
    430
      /// @inheritdoc IAccessManager
    431 0
      function setTargetClosed(address target, bool closed) public virtual onlyAuthorized {
    432 0
        _setTargetClosed(target, closed);
    433
      }
    434
    435
      /**
    436
       * @dev Set the closed flag for a contract. This is an internal setter with no access restrictions.
    437
       *
    438
       * Emits a {TargetClosed} event.
    439
       */
    440 0
      function _setTargetClosed(address target, bool closed) internal virtual {
    441 0
        _targets[target].closed = closed;
    442 0
        emit TargetClosed(target, closed);
    443
      }
    444
    445
      // ============================================== DELAYED OPERATIONS ==============================================
    446
      /// @inheritdoc IAccessManager
    447 0
      function getSchedule(bytes32 id) public view virtual returns (uint48) {
    448 0
        uint48 timepoint = _schedules[id].timepoint;
    449 0
        return _isExpired(timepoint) ? 0 : timepoint;
    450
      }
    451
    452
      /// @inheritdoc IAccessManager
    453 0
      function getNonce(bytes32 id) public view virtual returns (uint32) {
    454 0
        return _schedules[id].nonce;
    455
      }
    456
    457
      /// @inheritdoc IAccessManager
    458 0
      function schedule(
    459
        address target,
    460
        bytes calldata data,
    461
        uint48 when
    462 0
      ) public virtual returns (bytes32 operationId, uint32 nonce) {
    463
        address caller = _msgSender();
    464
    465
        // Fetch restrictions that apply to the caller on the targeted function
    466 0
        (, uint32 setback) = _canCallExtended(caller, target, data);
    467
    468 0
        uint48 minWhen = Time.timestamp() + setback;
    469
    470
        // If call with delay is not authorized, or if requested timing is too soon, revert
    471 0
        if (setback == 0 || (when > 0 && when < minWhen)) {
    472 0
          revert AccessManagerUnauthorizedCall(caller, target, _checkSelector(data));
    473
        }
    474
    475
        // Reuse variable due to stack too deep
    476 0
        when = uint48(Math.max(when, minWhen)); // cast is safe: both inputs are uint48
    477
    478
        // If caller is authorised, schedule operation
    479 0
        operationId = hashOperation(caller, target, data);
    480
    481 0
        _checkNotScheduled(operationId);
    482
    483
        unchecked {
    484
          // It's not feasible to overflow the nonce in less than 1000 years
    485 0
          nonce = _schedules[operationId].nonce + 1;
    486
        }
    487 0
        _schedules[operationId].timepoint = when;
    488 0
        _schedules[operationId].nonce = nonce;
    489 0
        emit OperationScheduled(operationId, nonce, when, caller, target, data);
    490
    491
        // Using named return values because otherwise we get stack too deep
    492
      }
    493
    494
      /**
    495
       * @dev Reverts if the operation is currently scheduled and has not expired.
    496
       *
    497
       * NOTE: This function was introduced due to stack too deep errors in schedule.
    498
       */
    499 0
      function _checkNotScheduled(bytes32 operationId) private view {
    500 0
        uint48 prevTimepoint = _schedules[operationId].timepoint;
    501 0
        if (prevTimepoint != 0 && !_isExpired(prevTimepoint)) {
    502 0
          revert AccessManagerAlreadyScheduled(operationId);
    503
        }
    504
      }
    505
    506
      /// @inheritdoc IAccessManager
    507
      // Reentrancy is not an issue because permissions are checked on msg.sender. Additionally,
    508
      // _consumeScheduledOp guarantees a scheduled operation is only executed once.
    509
      // slither-disable-next-line reentrancy-no-eth
    510 0
      function execute(address target, bytes calldata data) public payable virtual returns (uint32) {
    511
        address caller = _msgSender();
    512
    513
        // Fetch restrictions that apply to the caller on the targeted function
    514 0
        (bool immediate, uint32 setback) = _canCallExtended(caller, target, data);
    515
    516
        // If call is not authorized, revert
    517 0
        if (!immediate && setback == 0) {
    518 0
          revert AccessManagerUnauthorizedCall(caller, target, _checkSelector(data));
    519
        }
    520
    521 0
        bytes32 operationId = hashOperation(caller, target, data);
    522 0
        uint32 nonce;
    523
    524
        // If caller is authorised, check operation was scheduled early enough
    525
        // Consume an available schedule even if there is no currently enforced delay
    526 0
        if (setback != 0 || getSchedule(operationId) != 0) {
    527 0
          nonce = _consumeScheduledOp(operationId);
    528
        }
    529
    530
        // Mark the target and selector as authorised
    531 0
        bytes32 executionIdBefore = _executionId;
    532 0
        _executionId = _hashExecutionId(target, _checkSelector(data));
    533
    534
        // Perform call
    535 0
        Address.functionCallWithValue(target, data, msg.value);
    536
    537
        // Reset execute identifier
    538 0
        _executionId = executionIdBefore;
    539
    540 0
        return nonce;
    541
      }
    542
    543
      /// @inheritdoc IAccessManager
    544 0
      function cancel(
    545
        address caller,
    546
        address target,
    547
        bytes calldata data
    548 0
      ) public virtual returns (uint32) {
    549
        address msgsender = _msgSender();
    550 0
        bytes4 selector = _checkSelector(data);
    551
    552 0
        bytes32 operationId = hashOperation(caller, target, data);
    553 0
        if (_schedules[operationId].timepoint == 0) {
    554 0
          revert AccessManagerNotScheduled(operationId);
    555 0
        } else if (caller != msgsender) {
    556
          // calls can only be canceled by the account that scheduled them, a global admin, or by a guardian of the required role.
    557 0
          (bool isAdmin, ) = hasRole(ADMIN_ROLE, msgsender);
    558 0
          (bool isGuardian, ) = hasRole(
    559 0
            getRoleGuardian(getTargetFunctionRole(target, selector)),
    560 0
            msgsender
    561
          );
    562 0
          if (!isAdmin && !isGuardian) {
    563 0
            revert AccessManagerUnauthorizedCancel(msgsender, caller, target, selector);
    564
          }
    565
        }
    566
    567 0
        delete _schedules[operationId].timepoint; // reset the timepoint, keep the nonce
    568 0
        uint32 nonce = _schedules[operationId].nonce;
    569 0
        emit OperationCanceled(operationId, nonce);
    570
    571 0
        return nonce;
    572
      }
    573
    574
      /// @inheritdoc IAccessManager
    575 0
      function consumeScheduledOp(address caller, bytes calldata data) public virtual {
    576
        address target = _msgSender();
    577 0
        if (
    578 0
          IAccessManaged(target).isConsumingScheduledOp() !=
    579 0
          IAccessManaged.isConsumingScheduledOp.selector
    580
        ) {
    581 0
          revert AccessManagerUnauthorizedConsume(target);
    582
        }
    583 0
        _consumeScheduledOp(hashOperation(caller, target, data));
    584
      }
    585
    586
      /**
    587
       * @dev Internal variant of {consumeScheduledOp} that operates on bytes32 operationId.
    588
       *
    589
       * Returns the nonce of the scheduled operation that is consumed.
    590
       */
    591 0
      function _consumeScheduledOp(bytes32 operationId) internal virtual returns (uint32) {
    592 0
        uint48 timepoint = _schedules[operationId].timepoint;
    593 0
        uint32 nonce = _schedules[operationId].nonce;
    594
    595 0
        if (timepoint == 0) {
    596 0
          revert AccessManagerNotScheduled(operationId);
    597 0
        } else if (timepoint > Time.timestamp()) {
    598 0
          revert AccessManagerNotReady(operationId);
    599 0
        } else if (_isExpired(timepoint)) {
    600 0
          revert AccessManagerExpired(operationId);
    601
        }
    602
    603 0
        delete _schedules[operationId].timepoint; // reset the timepoint, keep the nonce
    604 0
        emit OperationExecuted(operationId, nonce);
    605
    606 0
        return nonce;
    607
      }
    608
    609
      /// @inheritdoc IAccessManager
    610 0
      function hashOperation(
    611
        address caller,
    612
        address target,
    613
        bytes calldata data
    614 0
      ) public view virtual returns (bytes32) {
    615 0
        return keccak256(abi.encode(caller, target, data));
    616
      }
    617
    618
      // ==================================================== OTHERS ====================================================
    619
      /// @inheritdoc IAccessManager
    620 0
      function updateAuthority(address target, address newAuthority) public virtual onlyAuthorized {
    621 0
        IAccessManaged(target).setAuthority(newAuthority);
    622
      }
    623
    624
      // ================================================= ADMIN LOGIC ==================================================
    625
      /**
    626
       * @dev Check if the current call is authorized according to admin and roles logic.
    627
       *
    628
       * WARNING: Carefully review the considerations of {AccessManaged-restricted} since they apply to this modifier.
    629
       */
    630 0
      function _checkAuthorized() private {
    631 0
        address caller = _msgSender();
    632 0
        (bool immediate, uint32 delay) = _canCallSelf(caller, _msgData());
    633 0
        if (!immediate) {
    634 0
          if (delay == 0) {
    635 0
            (, uint64 requiredRole, ) = _getAdminRestrictions(_msgData());
    636 0
            revert AccessManagerUnauthorizedAccount(caller, requiredRole);
    637
          } else {
    638 0
            _consumeScheduledOp(hashOperation(caller, address(this), _msgData()));
    639
          }
    640
        }
    641
      }
    642
    643
      /**
    644
       * @dev Get the admin restrictions of a given function call based on the function and arguments involved.
    645
       *
    646
       * Returns:
    647
       * - bool restricted: does this data match a restricted operation
    648
       * - uint64: which role is this operation restricted to
    649
       * - uint32: minimum delay to enforce for that operation (max between operation's delay and admin's execution delay)
    650
       */
    651 0
      function _getAdminRestrictions(
    652
        bytes calldata data
    653 0
      ) private view returns (bool adminRestricted, uint64 roleAdminId, uint32 executionDelay) {
    654 0
        if (data.length < 4) {
    655 0
          return (false, 0, 0);
    656
        }
    657
    658 0
        bytes4 selector = _checkSelector(data);
    659
    660
        // Restricted to ADMIN with no delay beside any execution delay the caller may have
    661 0
        if (
    662 0
          selector == this.labelRole.selector ||
    663 0
          selector == this.setRoleAdmin.selector ||
    664 0
          selector == this.setRoleGuardian.selector ||
    665 0
          selector == this.setGrantDelay.selector ||
    666 0
          selector == this.setTargetAdminDelay.selector
    667
        ) {
    668 0
          return (true, ADMIN_ROLE, 0);
    669
        }
    670
    671
        // Restricted to ADMIN with the admin delay corresponding to the target
    672 0
        if (
    673 0
          selector == this.updateAuthority.selector ||
    674 0
          selector == this.setTargetClosed.selector ||
    675 0
          selector == this.setTargetFunctionRole.selector
    676
        ) {
    677
          // First argument is a target.
    678 0
          address target = abi.decode(data[0x04:0x24], (address));
    679 0
          uint32 delay = getTargetAdminDelay(target);
    680 0
          return (true, ADMIN_ROLE, delay);
    681
        }
    682
    683
        // Restricted to that role's admin with no delay beside any execution delay the caller may have.
    684 0
        if (selector == this.grantRole.selector || selector == this.revokeRole.selector) {
    685
          // First argument is a roleId.
    686 0
          uint64 roleId = abi.decode(data[0x04:0x24], (uint64));
    687 0
          return (true, getRoleAdmin(roleId), 0);
    688
        }
    689
    690 0
        return (false, getTargetFunctionRole(address(this), selector), 0);
    691
      }
    692
    693
      // =================================================== HELPERS ====================================================
    694
      /**
    695
       * @dev An extended version of {canCall} for internal usage that checks {_canCallSelf}
    696
       * when the target is this contract.
    697
       *
    698
       * Returns:
    699
       * - bool immediate: whether the operation can be executed immediately (with no delay)
    700
       * - uint32 delay: the execution delay
    701
       */
    702 0
      function _canCallExtended(
    703
        address caller,
    704
        address target,
    705
        bytes calldata data
    706 0
      ) private view returns (bool immediate, uint32 delay) {
    707 0
        if (target == address(this)) {
    708 0
          return _canCallSelf(caller, data);
    709
        } else {
    710 0
          return data.length < 4 ? (false, 0) : canCall(caller, target, _checkSelector(data));
    711
        }
    712
      }
    713
    714
      /**
    715
       * @dev A version of {canCall} that checks for restrictions in this contract.
    716
       */
    717 0
      function _canCallSelf(
    718
        address caller,
    719
        bytes calldata data
    720 0
      ) private view returns (bool immediate, uint32 delay) {
    721 0
        if (data.length < 4) {
    722 0
          return (false, 0);
    723
        }
    724
    725 0
        if (caller == address(this)) {
    726
          // Caller is AccessManager, this means the call was sent through {execute} and it already checked
    727
          // permissions. We verify that the call "identifier", which is set during {execute}, is correct.
    728 0
          return (_isExecuting(address(this), _checkSelector(data)), 0);
    729
        }
    730
    731 0
        (bool adminRestricted, uint64 roleId, uint32 operationDelay) = _getAdminRestrictions(data);
    732
    733
        // isTargetClosed apply to non-admin-restricted function
    734 0
        if (!adminRestricted && isTargetClosed(address(this))) {
    735 0
          return (false, 0);
    736
        }
    737
    738 0
        (bool inRole, uint32 executionDelay) = hasRole(roleId, caller);
    739 0
        if (!inRole) {
    740 0
          return (false, 0);
    741
        }
    742
    743
        // downcast is safe because both options are uint32
    744 0
        delay = uint32(Math.max(operationDelay, executionDelay));
    745 0
        return (delay == 0, delay);
    746
      }
    747
    748
      /**
    749
       * @dev Returns true if a call with `target` and `selector` is being executed via {executed}.
    750
       */
    751 0
      function _isExecuting(address target, bytes4 selector) private view returns (bool) {
    752 0
        return _executionId == _hashExecutionId(target, selector);
    753
      }
    754
    755
      /**
    756
       * @dev Returns true if a schedule timepoint is past its expiration deadline.
    757
       */
    758 0
      function _isExpired(uint48 timepoint) private view returns (bool) {
    759 0
        return timepoint + expiration() <= Time.timestamp();
    760
      }
    761
    762
      /**
    763
       * @dev Extracts the selector from calldata. Panics if data is not at least 4 bytes
    764
       */
    765 0
      function _checkSelector(bytes calldata data) private pure returns (bytes4) {
    766 0
        return bytes4(data[0:4]);
    767
      }
    768
    769
      /**
    770
       * @dev Hashing function for execute protection
    771
       */
    772 0
      function _hashExecutionId(address target, bytes4 selector) private pure returns (bytes32) {
    773 0
        return keccak256(abi.encode(target, selector));
    774
      }
    775
    }
    7% src/dependencies/openzeppelin/Address.sol
    Lines covered: 2 / 27 (7%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/Address.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Errors} from './Errors.sol';
    7
    8
    /**
    9
     * @dev Collection of functions related to the address type
    10
     */
    11 0
    library Address {
    12
      /**
    13
       * @dev There's no code at `target` (it is not a contract).
    14
       */
    15
      error AddressEmptyCode(address target);
    16
    17
      /**
    18
       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
    19
       * `recipient`, forwarding all available gas and reverting on errors.
    20
       *
    21
       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
    22
       * of certain opcodes, possibly making contracts go over the 2300 gas limit
    23
       * imposed by `transfer`, making them unable to receive funds via
    24
       * `transfer`. {sendValue} removes this limitation.
    25
       *
    26
       * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
    27
       *
    28
       * IMPORTANT: because control is transferred to `recipient`, care must be
    29
       * taken to not create reentrancy vulnerabilities. Consider using
    30
       * {ReentrancyGuard} or the
    31
       * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
    32
       */
    33 0
      function sendValue(address payable recipient, uint256 amount) internal {
    34 0
        if (address(this).balance < amount) {
    35 0
          revert Errors.InsufficientBalance(address(this).balance, amount);
    36
        }
    37
    38 0
        (bool success, bytes memory returndata) = recipient.call{value: amount}('');
    39 0
        if (!success) {
    40 0
          _revert(returndata);
    41
        }
    42
      }
    43
    44
      /**
    45
       * @dev Performs a Solidity function call using a low level `call`. A
    46
       * plain `call` is an unsafe replacement for a function call: use this
    47
       * function instead.
    48
       *
    49
       * If `target` reverts with a revert reason or custom error, it is bubbled
    50
       * up by this function (like regular Solidity function calls). However, if
    51
       * the call reverted with no returned reason, this function reverts with a
    52
       * {Errors.FailedCall} error.
    53
       *
    54
       * Returns the raw returned data. To convert to the expected return value,
    55
       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
    56
       *
    57
       * Requirements:
    58
       *
    59
       * - `target` must be a contract.
    60
       * - calling `target` with `data` must not revert.
    61
       */
    62
      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
    63
        return functionCallWithValue(target, data, 0);
    64
      }
    65
    66
      /**
    67
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    68
       * but also transferring `value` wei to `target`.
    69
       *
    70
       * Requirements:
    71
       *
    72
       * - the calling contract must have an ETH balance of at least `value`.
    73
       * - the called Solidity function must be `payable`.
    74
       */
    75 0
      function functionCallWithValue(
    76
        address target,
    77
        bytes memory data,
    78
        uint256 value
    79 0
      ) internal returns (bytes memory) {
    80 0
        if (address(this).balance < value) {
    81 0
          revert Errors.InsufficientBalance(address(this).balance, value);
    82
        }
    83 0
        (bool success, bytes memory returndata) = target.call{value: value}(data);
    84 0
        return verifyCallResultFromTarget(target, success, returndata);
    85
      }
    86
    87
      /**
    88
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    89
       * but performing a static call.
    90
       */
    91
      function functionStaticCall(
    92
        address target,
    93
        bytes memory data
    94
      ) internal view returns (bytes memory) {
    95
        (bool success, bytes memory returndata) = target.staticcall(data);
    96
        return verifyCallResultFromTarget(target, success, returndata);
    97
      }
    98
    99
      /**
    100
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    101
       * but performing a delegate call.
    102
       */
    103 0
      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
    104 0
        (bool success, bytes memory returndata) = target.delegatecall(data);
    105 0
        return verifyCallResultFromTarget(target, success, returndata);
    106
      }
    107
    108
      /**
    109
       * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
    110
       * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
    111
       * of an unsuccessful call.
    112
       */
    113 0
      function verifyCallResultFromTarget(
    114
        address target,
    115
        bool success,
    116
        bytes memory returndata
    117 0
      ) internal view returns (bytes memory) {
    118 0
        if (!success) {
    119 0
          _revert(returndata);
    120
        } else {
    121
          // only check if target is a contract if the call was successful and the return data is empty
    122
          // otherwise we already know that it was a contract
    123 0
          if (returndata.length == 0 && target.code.length == 0) {
    124 0
            revert AddressEmptyCode(target);
    125
          }
    126 0
          return returndata;
    127
        }
    128
      }
    129
    130
      /**
    131
       * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
    132
       * revert reason or with a default {Errors.FailedCall} error.
    133
       */
    134
      function verifyCallResult(
    135
        bool success,
    136
        bytes memory returndata
    137
      ) internal pure returns (bytes memory) {
    138
        if (!success) {
    139
          _revert(returndata);
    140
        } else {
    141
          return returndata;
    142
        }
    143
      }
    144
    145
      /**
    146
       * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
    147
       */
    148
      function _revert(bytes memory returndata) private pure {
    149
        // Look for revert reason and bubble it up if present
    150
        if (returndata.length > 0) {
    151
          // The easiest way to bubble the revert reason is using memory via assembly
    152
          assembly ('memory-safe') {
    153 0
            revert(add(returndata, 0x20), mload(returndata))
    154
          }
    155
        } else {
    156 0
          revert Errors.FailedCall();
    157
        }
    158
      }
    159
    }
    83% src/dependencies/openzeppelin/Arrays.sol
    Lines covered: 25 / 30 (83%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/Arrays.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/Arrays.js.
    4
    5
    pragma solidity ^0.8.20;
    6
    7
    import {Comparators} from './Comparators.sol';
    8
    import {SlotDerivation} from './SlotDerivation.sol';
    9
    import {StorageSlot} from './StorageSlot.sol';
    10
    import {Math} from './Math.sol';
    11
    12
    /**
    13
     * @dev Collection of functions related to array types.
    14
     */
    15 0
    library Arrays {
    16
      using SlotDerivation for bytes32;
    17
      using StorageSlot for bytes32;
    18
    19
      /**
    20
       * @dev Sort an array of uint256 (in memory) following the provided comparator function.
    21
       *
    22
       * This function does the sorting "in place", meaning that it overrides the input. The object is returned for
    23
       * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
    24
       *
    25
       * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
    26
       * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
    27
       * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
    28
       * consume more gas than is available in a block, leading to potential DoS.
    29
       *
    30
       * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
    31
       */
    32
      function sort(
    33
        uint256[] memory array,
    34
        function(uint256, uint256) pure returns (bool) comp
    35
      ) internal pure returns (uint256[] memory) {
    36 10×
        _quickSort(_begin(array), _end(array), comp);
    37
        return array;
    38
      }
    39
    40
      /**
    41
       * @dev Variant of {sort} that sorts an array of uint256 in increasing order.
    42
       */
    43
      function sort(uint256[] memory array) internal pure returns (uint256[] memory) {
    44
        sort(array, Comparators.lt);
    45
        return array;
    46
      }
    47
    48
      /**
    49
       * @dev Sort an array of address (in memory) following the provided comparator function.
    50
       *
    51
       * This function does the sorting "in place", meaning that it overrides the input. The object is returned for
    52
       * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
    53
       *
    54
       * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
    55
       * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
    56
       * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
    57
       * consume more gas than is available in a block, leading to potential DoS.
    58
       *
    59
       * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
    60
       */
    61
      function sort(
    62
        address[] memory array,
    63
        function(address, address) pure returns (bool) comp
    64
      ) internal pure returns (address[] memory) {
    65
        sort(_castToUint256Array(array), _castToUint256Comp(comp));
    66
        return array;
    67
      }
    68
    69
      /**
    70
       * @dev Variant of {sort} that sorts an array of address in increasing order.
    71
       */
    72
      function sort(address[] memory array) internal pure returns (address[] memory) {
    73
        sort(_castToUint256Array(array), Comparators.lt);
    74
        return array;
    75
      }
    76
    77
      /**
    78
       * @dev Sort an array of bytes32 (in memory) following the provided comparator function.
    79
       *
    80
       * This function does the sorting "in place", meaning that it overrides the input. The object is returned for
    81
       * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
    82
       *
    83
       * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
    84
       * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
    85
       * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
    86
       * consume more gas than is available in a block, leading to potential DoS.
    87
       *
    88
       * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
    89
       */
    90
      function sort(
    91
        bytes32[] memory array,
    92
        function(bytes32, bytes32) pure returns (bool) comp
    93
      ) internal pure returns (bytes32[] memory) {
    94
        sort(_castToUint256Array(array), _castToUint256Comp(comp));
    95
        return array;
    96
      }
    97
    98
      /**
    99
       * @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
    100
       */
    101
      function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
    102
        sort(_castToUint256Array(array), Comparators.lt);
    103
        return array;
    104
      }
    105
    106
      /**
    107
       * @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops
    108
       * at end (exclusive). Sorting follows the `comp` comparator.
    109
       *
    110
       * Invariant: `begin <= end`. This is the case when initially called by {sort} and is preserved in subcalls.
    111
       *
    112
       * IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should
    113
       * be used only if the limits are within a memory array.
    114
       */
    115
      function _quickSort(
    116
        uint256 begin,
    117
        uint256 end,
    118
        function(uint256, uint256) pure returns (bool) comp
    119
      ) private pure {
    120
        unchecked {
    121
          if (end - begin < 0x40) return;
    122
    123
          // Use first element as pivot
    124
          uint256 pivot = _mload(begin);
    125
          // Position where the pivot should be at the end of the loop
    126
          uint256 pos = begin;
    127
    128 15×
          for (uint256 it = begin + 0x20; it < end; it += 0x20) {
    129 13×
            if (comp(_mload(it), pivot)) {
    130
              // If the value stored at the iterator's position comes before the pivot, we increment the
    131
              // position of the pivot and move the value there.
    132
              pos += 0x20;
    133
              _swap(pos, it);
    134
            }
    135
          }
    136
    137
          _swap(begin, pos); // Swap pivot into place
    138
          _quickSort(begin, pos, comp); // Sort the left side of the pivot
    139
          _quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot
    140
        }
    141
      }
    142
    143
      /**
    144
       * @dev Pointer to the memory location of the first element of `array`.
    145
       */
    146
      function _begin(uint256[] memory array) private pure returns (uint256 ptr) {
    147
        assembly ('memory-safe') {
    148
          ptr := add(array, 0x20)
    149
        }
    150
      }
    151
    152
      /**
    153
       * @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word
    154
       * that comes just after the last element of the array.
    155
       */
    156
      function _end(uint256[] memory array) private pure returns (uint256 ptr) {
    157
        unchecked {
    158
          return _begin(array) + array.length * 0x20;
    159
        }
    160
      }
    161
    162
      /**
    163
       * @dev Load memory word (as a uint256) at location `ptr`.
    164
       */
    165
      function _mload(uint256 ptr) private pure returns (uint256 value) {
    166
        assembly {
    167
          value := mload(ptr)
    168
        }
    169
      }
    170
    171
      /**
    172
       * @dev Swaps the elements memory location `ptr1` and `ptr2`.
    173
       */
    174
      function _swap(uint256 ptr1, uint256 ptr2) private pure {
    175
        assembly {
    176
          let value1 := mload(ptr1)
    177
          let value2 := mload(ptr2)
    178
          mstore(ptr1, value2)
    179
          mstore(ptr2, value1)
    180
        }
    181
      }
    182
    183
      /// @dev Helper: low level cast address memory array to uint256 memory array
    184
      function _castToUint256Array(
    185
        address[] memory input
    186
      ) private pure returns (uint256[] memory output) {
    187
        assembly {
    188
          output := input
    189
        }
    190
      }
    191
    192
      /// @dev Helper: low level cast bytes32 memory array to uint256 memory array
    193
      function _castToUint256Array(
    194
        bytes32[] memory input
    195
      ) private pure returns (uint256[] memory output) {
    196
        assembly {
    197
          output := input
    198
        }
    199
      }
    200
    201
      /// @dev Helper: low level cast address comp function to uint256 comp function
    202
      function _castToUint256Comp(
    203
        function(address, address) pure returns (bool) input
    204
      ) private pure returns (function(uint256, uint256) pure returns (bool) output) {
    205
        assembly {
    206
          output := input
    207
        }
    208
      }
    209
    210
      /// @dev Helper: low level cast bytes32 comp function to uint256 comp function
    211
      function _castToUint256Comp(
    212
        function(bytes32, bytes32) pure returns (bool) input
    213
      ) private pure returns (function(uint256, uint256) pure returns (bool) output) {
    214
        assembly {
    215
          output := input
    216
        }
    217
      }
    218
    219
      /**
    220
       * @dev Searches a sorted `array` and returns the first index that contains
    221
       * a value greater or equal to `element`. If no such index exists (i.e. all
    222
       * values in the array are strictly less than `element`), the array length is
    223
       * returned. Time complexity O(log n).
    224
       *
    225
       * NOTE: The `array` is expected to be sorted in ascending order, and to
    226
       * contain no repeated elements.
    227
       *
    228
       * IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks
    229
       * support for repeated elements in the array. The {lowerBound} function should
    230
       * be used instead.
    231
       */
    232
      function findUpperBound(
    233
        uint256[] storage array,
    234
        uint256 element
    235
      ) internal view returns (uint256) {
    236
        uint256 low = 0;
    237
        uint256 high = array.length;
    238
    239
        if (high == 0) {
    240
          return 0;
    241
        }
    242
    243
        while (low < high) {
    244
          uint256 mid = Math.average(low, high);
    245
    246
          // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
    247
          // because Math.average rounds towards zero (it does integer division with truncation).
    248
          if (unsafeAccess(array, mid).value > element) {
    249
            high = mid;
    250
          } else {
    251
            low = mid + 1;
    252
          }
    253
        }
    254
    255
        // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
    256
        if (low > 0 && unsafeAccess(array, low - 1).value == element) {
    257
          return low - 1;
    258
        } else {
    259
          return low;
    260
        }
    261
      }
    262
    263
      /**
    264
       * @dev Searches an `array` sorted in ascending order and returns the first
    265
       * index that contains a value greater or equal than `element`. If no such index
    266
       * exists (i.e. all values in the array are strictly less than `element`), the array
    267
       * length is returned. Time complexity O(log n).
    268
       *
    269
       * See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound].
    270
       */
    271
      function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
    272
        uint256 low = 0;
    273
        uint256 high = array.length;
    274
    275
        if (high == 0) {
    276
          return 0;
    277
        }
    278
    279
        while (low < high) {
    280
          uint256 mid = Math.average(low, high);
    281
    282
          // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
    283
          // because Math.average rounds towards zero (it does integer division with truncation).
    284
          if (unsafeAccess(array, mid).value < element) {
    285
            // this cannot overflow because mid < high
    286
            unchecked {
    287
              low = mid + 1;
    288
            }
    289
          } else {
    290
            high = mid;
    291
          }
    292
        }
    293
    294
        return low;
    295
      }
    296
    297
      /**
    298
       * @dev Searches an `array` sorted in ascending order and returns the first
    299
       * index that contains a value strictly greater than `element`. If no such index
    300
       * exists (i.e. all values in the array are strictly less than `element`), the array
    301
       * length is returned. Time complexity O(log n).
    302
       *
    303
       * See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound].
    304
       */
    305
      function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
    306
        uint256 low = 0;
    307
        uint256 high = array.length;
    308
    309
        if (high == 0) {
    310
          return 0;
    311
        }
    312
    313
        while (low < high) {
    314
          uint256 mid = Math.average(low, high);
    315
    316
          // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
    317
          // because Math.average rounds towards zero (it does integer division with truncation).
    318
          if (unsafeAccess(array, mid).value > element) {
    319
            high = mid;
    320
          } else {
    321
            // this cannot overflow because mid < high
    322
            unchecked {
    323
              low = mid + 1;
    324
            }
    325
          }
    326
        }
    327
    328
        return low;
    329
      }
    330
    331
      /**
    332
       * @dev Same as {lowerBound}, but with an array in memory.
    333
       */
    334
      function lowerBoundMemory(
    335
        uint256[] memory array,
    336
        uint256 element
    337
      ) internal pure returns (uint256) {
    338
        uint256 low = 0;
    339
        uint256 high = array.length;
    340
    341
        if (high == 0) {
    342
          return 0;
    343
        }
    344
    345
        while (low < high) {
    346
          uint256 mid = Math.average(low, high);
    347
    348
          // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
    349
          // because Math.average rounds towards zero (it does integer division with truncation).
    350
          if (unsafeMemoryAccess(array, mid) < element) {
    351
            // this cannot overflow because mid < high
    352
            unchecked {
    353
              low = mid + 1;
    354
            }
    355
          } else {
    356
            high = mid;
    357
          }
    358
        }
    359
    360
        return low;
    361
      }
    362
    363
      /**
    364
       * @dev Same as {upperBound}, but with an array in memory.
    365
       */
    366
      function upperBoundMemory(
    367
        uint256[] memory array,
    368
        uint256 element
    369
      ) internal pure returns (uint256) {
    370
        uint256 low = 0;
    371
        uint256 high = array.length;
    372
    373
        if (high == 0) {
    374
          return 0;
    375
        }
    376
    377
        while (low < high) {
    378
          uint256 mid = Math.average(low, high);
    379
    380
          // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
    381
          // because Math.average rounds towards zero (it does integer division with truncation).
    382
          if (unsafeMemoryAccess(array, mid) > element) {
    383
            high = mid;
    384
          } else {
    385
            // this cannot overflow because mid < high
    386
            unchecked {
    387
              low = mid + 1;
    388
            }
    389
          }
    390
        }
    391
    392
        return low;
    393
      }
    394
    395
      /**
    396
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    397
       *
    398
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    399
       */
    400
      function unsafeAccess(
    401
        address[] storage arr,
    402
        uint256 pos
    403
      ) internal pure returns (StorageSlot.AddressSlot storage) {
    404
        bytes32 slot;
    405
        assembly ('memory-safe') {
    406
          slot := arr.slot
    407
        }
    408
        return slot.deriveArray().offset(pos).getAddressSlot();
    409
      }
    410
    411
      /**
    412
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    413
       *
    414
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    415
       */
    416 0
      function unsafeAccess(
    417
        bytes32[] storage arr,
    418
        uint256 pos
    419 0
      ) internal pure returns (StorageSlot.Bytes32Slot storage) {
    420
        bytes32 slot;
    421
        assembly ('memory-safe') {
    422 0
          slot := arr.slot
    423
        }
    424 0
        return slot.deriveArray().offset(pos).getBytes32Slot();
    425
      }
    426
    427
      /**
    428
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    429
       *
    430
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    431
       */
    432
      function unsafeAccess(
    433
        uint256[] storage arr,
    434
        uint256 pos
    435
      ) internal pure returns (StorageSlot.Uint256Slot storage) {
    436
        bytes32 slot;
    437
        assembly ('memory-safe') {
    438
          slot := arr.slot
    439
        }
    440
        return slot.deriveArray().offset(pos).getUint256Slot();
    441
      }
    442
    443
      /**
    444
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    445
       *
    446
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    447
       */
    448
      function unsafeAccess(
    449
        bytes[] storage arr,
    450
        uint256 pos
    451
      ) internal pure returns (StorageSlot.BytesSlot storage) {
    452
        bytes32 slot;
    453
        assembly ('memory-safe') {
    454
          slot := arr.slot
    455
        }
    456
        return slot.deriveArray().offset(pos).getBytesSlot();
    457
      }
    458
    459
      /**
    460
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    461
       *
    462
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    463
       */
    464
      function unsafeAccess(
    465
        string[] storage arr,
    466
        uint256 pos
    467
      ) internal pure returns (StorageSlot.StringSlot storage) {
    468
        bytes32 slot;
    469
        assembly ('memory-safe') {
    470
          slot := arr.slot
    471
        }
    472
        return slot.deriveArray().offset(pos).getStringSlot();
    473
      }
    474
    475
      /**
    476
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    477
       *
    478
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    479
       */
    480
      function unsafeMemoryAccess(
    481
        address[] memory arr,
    482
        uint256 pos
    483
      ) internal pure returns (address res) {
    484
        assembly {
    485
          res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
    486
        }
    487
      }
    488
    489
      /**
    490
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    491
       *
    492
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    493
       */
    494
      function unsafeMemoryAccess(
    495
        bytes32[] memory arr,
    496
        uint256 pos
    497
      ) internal pure returns (bytes32 res) {
    498
        assembly {
    499
          res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
    500
        }
    501
      }
    502
    503
      /**
    504
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    505
       *
    506
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    507
       */
    508
      function unsafeMemoryAccess(
    509
        uint256[] memory arr,
    510
        uint256 pos
    511
      ) internal pure returns (uint256 res) {
    512
        assembly {
    513
          res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
    514
        }
    515
      }
    516
    517
      /**
    518
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    519
       *
    520
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    521
       */
    522
      function unsafeMemoryAccess(
    523
        bytes[] memory arr,
    524
        uint256 pos
    525
      ) internal pure returns (bytes memory res) {
    526
        assembly {
    527
          res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
    528
        }
    529
      }
    530
    531
      /**
    532
       * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
    533
       *
    534
       * WARNING: Only use if you are certain `pos` is lower than the array length.
    535
       */
    536
      function unsafeMemoryAccess(
    537
        string[] memory arr,
    538
        uint256 pos
    539
      ) internal pure returns (string memory res) {
    540
        assembly {
    541
          res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
    542
        }
    543
      }
    544
    545
      /**
    546
       * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
    547
       *
    548
       * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
    549
       */
    550
      function unsafeSetLength(address[] storage array, uint256 len) internal {
    551
        assembly ('memory-safe') {
    552
          sstore(array.slot, len)
    553
        }
    554
      }
    555
    556
      /**
    557
       * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
    558
       *
    559
       * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
    560
       */
    561
      function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
    562
        assembly ('memory-safe') {
    563
          sstore(array.slot, len)
    564
        }
    565
      }
    566
    567
      /**
    568
       * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
    569
       *
    570
       * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
    571
       */
    572
      function unsafeSetLength(uint256[] storage array, uint256 len) internal {
    573
        assembly ('memory-safe') {
    574
          sstore(array.slot, len)
    575
        }
    576
      }
    577
    578
      /**
    579
       * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
    580
       *
    581
       * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
    582
       */
    583
      function unsafeSetLength(bytes[] storage array, uint256 len) internal {
    584
        assembly ('memory-safe') {
    585
          sstore(array.slot, len)
    586
        }
    587
      }
    588
    589
      /**
    590
       * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
    591
       *
    592
       * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
    593
       */
    594
      function unsafeSetLength(string[] storage array, uint256 len) internal {
    595
        assembly ('memory-safe') {
    596
          sstore(array.slot, len)
    597
        }
    598
      }
    599
    }
    90% src/dependencies/openzeppelin/AuthorityUtils.sol
    Lines covered: 10 / 11 (90%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (access/manager/AuthorityUtils.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {IAuthority} from './IAuthority.sol';
    7
    8 0
    library AuthorityUtils {
    9
      /**
    10
       * @dev Since `AccessManager` implements an extended IAuthority interface, invoking `canCall` with backwards compatibility
    11
       * for the preexisting `IAuthority` interface requires special care to avoid reverting on insufficient return data.
    12
       * This helper function takes care of invoking `canCall` in a backwards compatible way without reverting.
    13
       */
    14 27×
      function canCallWithDelay(
    15
        address authority,
    16
        address caller,
    17
        address target,
    18
        bytes4 selector
    19 18×
      ) internal view returns (bool immediate, uint32 delay) {
    20 93×
        bytes memory data = abi.encodeCall(IAuthority.canCall, (caller, target, selector));
    21
    22
        assembly ('memory-safe') {
    23
          mstore(0x00, 0x00)
    24 12×
          mstore(0x20, 0x00)
    25
    26 27×
          if staticcall(gas(), authority, add(data, 0x20), mload(data), 0x00, 0x40) {
    27 12×
            immediate := mload(0x00)
    28
            delay := mload(0x20)
    29
    30
            // If delay does not fit in a uint32, return 0 (no delay)
    31
            // equivalent to: if gt(delay, 0xFFFFFFFF) { delay := 0 }
    32 18×
            delay := mul(delay, iszero(shr(32, delay)))
    33
          }
    34
        }
    35
      }
    36
    }
    0% src/dependencies/openzeppelin/Bytes.sol
    Lines covered: 0 / 1 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/Bytes.sol)
    3
    4
    pragma solidity ^0.8.24;
    5
    6
    import {Math} from './Math.sol';
    7
    8
    /**
    9
     * @dev Bytes operations.
    10
     */
    11 0
    library Bytes {
    12
      /**
    13
       * @dev Forward search for `s` in `buffer`
    14
       * * If `s` is present in the buffer, returns the index of the first instance
    15
       * * If `s` is not present in the buffer, returns type(uint256).max
    16
       *
    17
       * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
    18
       */
    19
      function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) {
    20
        return indexOf(buffer, s, 0);
    21
      }
    22
    23
      /**
    24
       * @dev Forward search for `s` in `buffer` starting at position `pos`
    25
       * * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance
    26
       * * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max
    27
       *
    28
       * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
    29
       */
    30
      function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
    31
        uint256 length = buffer.length;
    32
        for (uint256 i = pos; i < length; ++i) {
    33
          if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
    34
            return i;
    35
          }
    36
        }
    37
        return type(uint256).max;
    38
      }
    39
    40
      /**
    41
       * @dev Backward search for `s` in `buffer`
    42
       * * If `s` is present in the buffer, returns the index of the last instance
    43
       * * If `s` is not present in the buffer, returns type(uint256).max
    44
       *
    45
       * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
    46
       */
    47
      function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) {
    48
        return lastIndexOf(buffer, s, type(uint256).max);
    49
      }
    50
    51
      /**
    52
       * @dev Backward search for `s` in `buffer` starting at position `pos`
    53
       * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance
    54
       * * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max
    55
       *
    56
       * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
    57
       */
    58
      function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
    59
        unchecked {
    60
          uint256 length = buffer.length;
    61
          for (uint256 i = Math.min(Math.saturatingAdd(pos, 1), length); i > 0; --i) {
    62
            if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) {
    63
              return i - 1;
    64
            }
    65
          }
    66
          return type(uint256).max;
    67
        }
    68
      }
    69
    70
      /**
    71
       * @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in
    72
       * memory.
    73
       *
    74
       * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
    75
       */
    76
      function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) {
    77
        return slice(buffer, start, buffer.length);
    78
      }
    79
    80
      /**
    81
       * @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in
    82
       * memory.
    83
       *
    84
       * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`]
    85
       */
    86
      function slice(
    87
        bytes memory buffer,
    88
        uint256 start,
    89
        uint256 end
    90
      ) internal pure returns (bytes memory) {
    91
        // sanitize
    92
        uint256 length = buffer.length;
    93
        end = Math.min(end, length);
    94
        start = Math.min(start, end);
    95
    96
        // allocate and copy
    97
        bytes memory result = new bytes(end - start);
    98
        assembly ('memory-safe') {
    99
          mcopy(add(result, 0x20), add(add(buffer, 0x20), start), sub(end, start))
    100
        }
    101
    102
        return result;
    103
      }
    104
    105
      /**
    106
       * @dev Reads a bytes32 from a bytes array without bounds checking.
    107
       *
    108
       * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
    109
       * assembly block as such would prevent some optimizations.
    110
       */
    111
      function _unsafeReadBytesOffset(
    112
        bytes memory buffer,
    113
        uint256 offset
    114
      ) private pure returns (bytes32 value) {
    115
        // This is not memory safe in the general case, but all calls to this private function are within bounds.
    116
        assembly ('memory-safe') {
    117
          value := mload(add(add(buffer, 0x20), offset))
    118
        }
    119
      }
    120
    }
    0% src/dependencies/openzeppelin/Comparators.sol
    Lines covered: 0 / 1 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Provides a set of functions to compare values.
    8
     *
    9
     * _Available since v5.1._
    10
     */
    11 0
    library Comparators {
    12
      function lt(uint256 a, uint256 b) internal pure returns (bool) {
    13
        return a < b;
    14
      }
    15
    16
      function gt(uint256 a, uint256 b) internal pure returns (bool) {
    17
        return a > b;
    18
      }
    19
    }
    100% src/dependencies/openzeppelin/Context.sol
    Lines covered: 3 / 3 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Provides information about the current execution context, including the
    8
     * sender of the transaction and its data. While these are generally available
    9
     * via msg.sender and msg.data, they should not be accessed in such a direct
    10
     * manner, since when dealing with meta-transactions the account sending and
    11
     * paying for execution may not be the actual sender (as far as an application
    12
     * is concerned).
    13
     *
    14
     * This contract is only required for intermediate, library-like contracts.
    15
     */
    16
    abstract contract Context {
    17
      function _msgSender() internal view virtual returns (address) {
    18 13×
        return msg.sender;
    19
      }
    20
    21
      function _msgData() internal view virtual returns (bytes calldata) {
    22
        return msg.data;
    23
      }
    24
    25
      function _contextSuffixLength() internal view virtual returns (uint256) {
    26
        return 0;
    27
      }
    28
    }
    11% src/dependencies/openzeppelin/ECDSA.sol
    Lines covered: 2 / 18 (11%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
    8
     *
    9
     * These functions can be used to verify that a message was signed by the holder
    10
     * of the private keys of a given address.
    11
     */
    12 0
    library ECDSA {
    13
      enum RecoverError {
    14
        NoError,
    15
        InvalidSignature,
    16
        InvalidSignatureLength,
    17
        InvalidSignatureS
    18
      }
    19
    20
      /**
    21
       * @dev The signature derives the `address(0)`.
    22
       */
    23
      error ECDSAInvalidSignature();
    24
    25
      /**
    26
       * @dev The signature has an invalid length.
    27
       */
    28
      error ECDSAInvalidSignatureLength(uint256 length);
    29
    30
      /**
    31
       * @dev The signature has an S value that is in the upper half order.
    32
       */
    33
      error ECDSAInvalidSignatureS(bytes32 s);
    34
    35
      /**
    36
       * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
    37
       * return address(0) without also returning an error description. Errors are documented using an enum (error type)
    38
       * and a bytes32 providing additional information about the error.
    39
       *
    40
       * If no error is returned, then the address can be used for verification purposes.
    41
       *
    42
       * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
    43
       * this function rejects them by requiring the `s` value to be in the lower
    44
       * half order, and the `v` value to be either 27 or 28.
    45
       *
    46
       * IMPORTANT: `hash` _must_ be the result of a hash operation for the
    47
       * verification to be secure: it is possible to craft signatures that
    48
       * recover to arbitrary addresses for non-hashed data. A safe way to ensure
    49
       * this is by receiving a hash of the original message (which may otherwise
    50
       * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
    51
       *
    52
       * Documentation for signature generation:
    53
       * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
    54
       * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
    55
       */
    56
      function tryRecover(
    57
        bytes32 hash,
    58
        bytes memory signature
    59 0
      ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
    60
        if (signature.length == 65) {
    61 0
          bytes32 r;
    62
          bytes32 s;
    63
          uint8 v;
    64
          // ecrecover takes the signature parameters, and the only way to get them
    65
          // currently is to use assembly.
    66
          assembly ('memory-safe') {
    67 0
            r := mload(add(signature, 0x20))
    68 0
            s := mload(add(signature, 0x40))
    69 0
            v := byte(0, mload(add(signature, 0x60)))
    70
          }
    71 0
          return tryRecover(hash, v, r, s);
    72
        } else {
    73 0
          return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
    74
        }
    75
      }
    76
    77
      /**
    78
       * @dev Returns the address that signed a hashed message (`hash`) with
    79
       * `signature`. This address can then be used for verification purposes.
    80
       *
    81
       * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
    82
       * this function rejects them by requiring the `s` value to be in the lower
    83
       * half order, and the `v` value to be either 27 or 28.
    84
       *
    85
       * IMPORTANT: `hash` _must_ be the result of a hash operation for the
    86
       * verification to be secure: it is possible to craft signatures that
    87
       * recover to arbitrary addresses for non-hashed data. A safe way to ensure
    88
       * this is by receiving a hash of the original message (which may otherwise
    89
       * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
    90
       */
    91
      function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
    92
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
    93
        _throwError(error, errorArg);
    94
        return recovered;
    95
      }
    96
    97
      /**
    98
       * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
    99
       *
    100
       * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
    101
       */
    102
      function tryRecover(
    103
        bytes32 hash,
    104
        bytes32 r,
    105
        bytes32 vs
    106
      ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
    107
        unchecked {
    108
          bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
    109
          // We do not check for an overflow here since the shift operation results in 0 or 1.
    110
          uint8 v = uint8((uint256(vs) >> 255) + 27);
    111
          return tryRecover(hash, v, r, s);
    112
        }
    113
      }
    114
    115
      /**
    116
       * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
    117
       */
    118
      function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
    119
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
    120
        _throwError(error, errorArg);
    121
        return recovered;
    122
      }
    123
    124
      /**
    125
       * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
    126
       * `r` and `s` signature fields separately.
    127
       */
    128 0
      function tryRecover(
    129
        bytes32 hash,
    130
        uint8 v,
    131
        bytes32 r,
    132
        bytes32 s
    133 0
      ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
    134
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
    135
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
    136
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
    137
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
    138
        //
    139
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
    140
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
    141
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
    142
        // these malleable signatures as well.
    143 0
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
    144 0
          return (address(0), RecoverError.InvalidSignatureS, s);
    145
        }
    146
    147
        // If the signature is valid (and not malleable), return the signer address
    148 0
        address signer = ecrecover(hash, v, r, s);
    149 0
        if (signer == address(0)) {
    150 0
          return (address(0), RecoverError.InvalidSignature, bytes32(0));
    151
        }
    152
    153 0
        return (signer, RecoverError.NoError, bytes32(0));
    154
      }
    155
    156
      /**
    157
       * @dev Overload of {ECDSA-recover} that receives the `v`,
    158
       * `r` and `s` signature fields separately.
    159
       */
    160
      function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
    161
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
    162
        _throwError(error, errorArg);
    163
        return recovered;
    164
      }
    165
    166
      /**
    167
       * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
    168
       */
    169
      function _throwError(RecoverError error, bytes32 errorArg) private pure {
    170
        if (error == RecoverError.NoError) {
    171
          return; // no error: do nothing
    172
        } else if (error == RecoverError.InvalidSignature) {
    173
          revert ECDSAInvalidSignature();
    174
        } else if (error == RecoverError.InvalidSignatureLength) {
    175
          revert ECDSAInvalidSignatureLength(uint256(errorArg));
    176
        } else if (error == RecoverError.InvalidSignatureS) {
    177
          revert ECDSAInvalidSignatureS(errorArg);
    178
        }
    179
      }
    180
    }
    0% src/dependencies/openzeppelin/ERC1967Proxy.sol
    Lines covered: 0 / 5 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Proxy.sol)
    3
    4
    pragma solidity ^0.8.22;
    5
    6
    import {Proxy} from './Proxy.sol';
    7
    import {ERC1967Utils} from './ERC1967Utils.sol';
    8
    9
    /**
    10
     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
    11
     * implementation address that can be changed. This address is stored in storage in the location specified by
    12
     * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the
    13
     * implementation behind the proxy.
    14
     */
    15 0
    contract ERC1967Proxy is Proxy {
    16
      /**
    17
       * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
    18
       *
    19
       * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
    20
       * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
    21
       *
    22
       * Requirements:
    23
       *
    24
       * - If `data` is empty, `msg.value` must be zero.
    25
       */
    26 0
      constructor(address implementation, bytes memory _data) payable {
    27 0
        ERC1967Utils.upgradeToAndCall(implementation, _data);
    28
      }
    29
    30
      /**
    31
       * @dev Returns the current implementation address.
    32
       *
    33
       * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
    34
       * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
    35
       * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
    36
       */
    37 0
      function _implementation() internal view virtual override returns (address) {
    38 0
        return ERC1967Utils.getImplementation();
    39
      }
    40
    }
    0% src/dependencies/openzeppelin/ERC1967Utils.sol
    Lines covered: 0 / 26 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (proxy/ERC1967/ERC1967Utils.sol)
    3
    4
    pragma solidity ^0.8.21;
    5
    6
    import {IBeacon} from './IBeacon.sol';
    7
    import {IERC1967} from './IERC1967.sol';
    8
    import {Address} from './Address.sol';
    9
    import {StorageSlot} from './StorageSlot.sol';
    10
    11
    /**
    12
     * @dev This library provides getters and event emitting update functions for
    13
     * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
    14
     */
    15 0
    library ERC1967Utils {
    16
      /**
    17
       * @dev Storage slot with the address of the current implementation.
    18
       * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
    19
       */
    20
      // solhint-disable-next-line private-vars-leading-underscore
    21
      bytes32 internal constant IMPLEMENTATION_SLOT =
    22 0
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    23
    24
      /**
    25
       * @dev The `implementation` of the proxy is invalid.
    26
       */
    27
      error ERC1967InvalidImplementation(address implementation);
    28
    29
      /**
    30
       * @dev The `admin` of the proxy is invalid.
    31
       */
    32
      error ERC1967InvalidAdmin(address admin);
    33
    34
      /**
    35
       * @dev The `beacon` of the proxy is invalid.
    36
       */
    37
      error ERC1967InvalidBeacon(address beacon);
    38
    39
      /**
    40
       * @dev An upgrade function sees `msg.value > 0` that may be lost.
    41
       */
    42
      error ERC1967NonPayable();
    43
    44
      /**
    45
       * @dev Returns the current implementation address.
    46
       */
    47 0
      function getImplementation() internal view returns (address) {
    48 0
        return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
    49
      }
    50
    51
      /**
    52
       * @dev Stores a new address in the ERC-1967 implementation slot.
    53
       */
    54 0
      function _setImplementation(address newImplementation) private {
    55 0
        if (newImplementation.code.length == 0) {
    56 0
          revert ERC1967InvalidImplementation(newImplementation);
    57
        }
    58 0
        StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
    59
      }
    60
    61
      /**
    62
       * @dev Performs implementation upgrade with additional setup call if data is nonempty.
    63
       * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
    64
       * to avoid stuck value in the contract.
    65
       *
    66
       * Emits an {IERC1967-Upgraded} event.
    67
       */
    68 0
      function upgradeToAndCall(address newImplementation, bytes memory data) internal {
    69 0
        _setImplementation(newImplementation);
    70 0
        emit IERC1967.Upgraded(newImplementation);
    71
    72 0
        if (data.length > 0) {
    73 0
          Address.functionDelegateCall(newImplementation, data);
    74
        } else {
    75 0
          _checkNonPayable();
    76
        }
    77
      }
    78
    79
      /**
    80
       * @dev Storage slot with the admin of the contract.
    81
       * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
    82
       */
    83
      // solhint-disable-next-line private-vars-leading-underscore
    84
      bytes32 internal constant ADMIN_SLOT =
    85
        0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    86
    87
      /**
    88
       * @dev Returns the current admin.
    89
       *
    90
       * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
    91
       * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
    92
       * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
    93
       */
    94 0
      function getAdmin() internal view returns (address) {
    95 0
        return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
    96
      }
    97
    98
      /**
    99
       * @dev Stores a new address in the ERC-1967 admin slot.
    100
       */
    101 0
      function _setAdmin(address newAdmin) private {
    102 0
        if (newAdmin == address(0)) {
    103 0
          revert ERC1967InvalidAdmin(address(0));
    104
        }
    105 0
        StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
    106
      }
    107
    108
      /**
    109
       * @dev Changes the admin of the proxy.
    110
       *
    111
       * Emits an {IERC1967-AdminChanged} event.
    112
       */
    113 0
      function changeAdmin(address newAdmin) internal {
    114 0
        emit IERC1967.AdminChanged(getAdmin(), newAdmin);
    115 0
        _setAdmin(newAdmin);
    116
      }
    117
    118
      /**
    119
       * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
    120
       * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
    121
       */
    122
      // solhint-disable-next-line private-vars-leading-underscore
    123
      bytes32 internal constant BEACON_SLOT =
    124
        0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
    125
    126
      /**
    127
       * @dev Returns the current beacon.
    128
       */
    129
      function getBeacon() internal view returns (address) {
    130
        return StorageSlot.getAddressSlot(BEACON_SLOT).value;
    131
      }
    132
    133
      /**
    134
       * @dev Stores a new beacon in the ERC-1967 beacon slot.
    135
       */
    136
      function _setBeacon(address newBeacon) private {
    137
        if (newBeacon.code.length == 0) {
    138
          revert ERC1967InvalidBeacon(newBeacon);
    139
        }
    140
    141
        StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
    142
    143
        address beaconImplementation = IBeacon(newBeacon).implementation();
    144
        if (beaconImplementation.code.length == 0) {
    145
          revert ERC1967InvalidImplementation(beaconImplementation);
    146
        }
    147
      }
    148
    149
      /**
    150
       * @dev Change the beacon and trigger a setup call if data is nonempty.
    151
       * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
    152
       * to avoid stuck value in the contract.
    153
       *
    154
       * Emits an {IERC1967-BeaconUpgraded} event.
    155
       *
    156
       * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
    157
       * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
    158
       * efficiency.
    159
       */
    160
      function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
    161
        _setBeacon(newBeacon);
    162
        emit IERC1967.BeaconUpgraded(newBeacon);
    163
    164
        if (data.length > 0) {
    165
          Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
    166
        } else {
    167
          _checkNonPayable();
    168
        }
    169
      }
    170
    171
      /**
    172
       * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
    173
       * if an upgrade doesn't perform an initialization call.
    174
       */
    175 0
      function _checkNonPayable() private {
    176 0
        if (msg.value > 0) {
    177 0
          revert ERC1967NonPayable();
    178
        }
    179
      }
    180
    }
    64% src/dependencies/openzeppelin/ERC20.sol
    Lines covered: 42 / 65 (64%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {IERC20} from './IERC20.sol';
    7
    import {IERC20Metadata} from './IERC20Metadata.sol';
    8
    import {Context} from './Context.sol';
    9
    import {IERC20Errors} from './IERC20Errors.sol';
    10
    11
    /**
    12
     * @dev Implementation of the {IERC20} interface.
    13
     *
    14
     * This implementation is agnostic to the way tokens are created. This means
    15
     * that a supply mechanism has to be added in a derived contract using {_mint}.
    16
     *
    17
     * TIP: For a detailed writeup see our guide
    18
     * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
    19
     * to implement supply mechanisms].
    20
     *
    21
     * The default value of {decimals} is 18. To change this, you should override
    22
     * this function so it returns a different value.
    23
     *
    24
     * We have followed general OpenZeppelin Contracts guidelines: functions revert
    25
     * instead returning `false` on failure. This behavior is nonetheless
    26
     * conventional and does not conflict with the expectations of ERC-20
    27
     * applications.
    28
     */
    29
    abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    30
      mapping(address account => uint256) private _balances;
    31
    32
      mapping(address account => mapping(address spender => uint256)) private _allowances;
    33
    34
      uint256 private _totalSupply;
    35
    36
      string private _name;
    37
      string private _symbol;
    38
    39
      /**
    40
       * @dev Sets the values for {name} and {symbol}.
    41
       *
    42
       * Both values are immutable: they can only be set once during construction.
    43
       */
    44 0
      constructor(string memory name_, string memory symbol_) {
    45
        _name = name_;
    46
        _symbol = symbol_;
    47
      }
    48
    49
      /**
    50
       * @dev Returns the name of the token.
    51
       */
    52 24×
      function name() public view virtual returns (string memory) {
    53 0
        return _name;
    54
      }
    55
    56
      /**
    57
       * @dev Returns the symbol of the token, usually a shorter version of the
    58
       * name.
    59
       */
    60 0
      function symbol() public view virtual returns (string memory) {
    61 0
        return _symbol;
    62
      }
    63
    64
      /**
    65
       * @dev Returns the number of decimals used to get its user representation.
    66
       * For example, if `decimals` equals `2`, a balance of `505` tokens should
    67
       * be displayed to a user as `5.05` (`505 / 10 ** 2`).
    68
       *
    69
       * Tokens usually opt for a value of 18, imitating the relationship between
    70
       * Ether and Wei. This is the default value returned by this function, unless
    71
       * it's overridden.
    72
       *
    73
       * NOTE: This information is only used for _display_ purposes: it in
    74
       * no way affects any of the arithmetic of the contract, including
    75
       * {IERC20-balanceOf} and {IERC20-transfer}.
    76
       */
    77 0
      function decimals() public view virtual returns (uint8) {
    78 0
        return 18;
    79
      }
    80
    81
      /// @inheritdoc IERC20
    82
      function totalSupply() public view virtual returns (uint256) {
    83 0
        return _totalSupply;
    84
      }
    85
    86
      /// @inheritdoc IERC20
    87 20×
      function balanceOf(address account) public view virtual returns (uint256) {
    88 26×
        return _balances[account];
    89
      }
    90
    91
      /**
    92
       * @dev See {IERC20-transfer}.
    93
       *
    94
       * Requirements:
    95
       *
    96
       * - `to` cannot be the zero address.
    97
       * - the caller must have a balance of at least `value`.
    98
       */
    99 24×
      function transfer(address to, uint256 value) public virtual returns (bool) {
    100
        address owner = _msgSender();
    101 10×
        _transfer(owner, to, value);
    102
        return true;
    103
      }
    104
    105
      /// @inheritdoc IERC20
    106
      function allowance(address owner, address spender) public view virtual returns (uint256) {
    107 56×
        return _allowances[owner][spender];
    108
      }
    109
    110
      /**
    111
       * @dev See {IERC20-approve}.
    112
       *
    113
       * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
    114
       * `transferFrom`. This is semantically equivalent to an infinite approval.
    115
       *
    116
       * Requirements:
    117
       *
    118
       * - `spender` cannot be the zero address.
    119
       */
    120 24×
      function approve(address spender, uint256 value) public virtual returns (bool) {
    121
        address owner = _msgSender();
    122
        _approve(owner, spender, value);
    123
        return true;
    124
      }
    125
    126
      /**
    127
       * @dev See {IERC20-transferFrom}.
    128
       *
    129
       * Skips emitting an {Approval} event indicating an allowance update. This is not
    130
       * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
    131
       *
    132
       * NOTE: Does not update the allowance if the current allowance
    133
       * is the maximum `uint256`.
    134
       *
    135
       * Requirements:
    136
       *
    137
       * - `from` and `to` cannot be the zero address.
    138
       * - `from` must have a balance of at least `value`.
    139
       * - the caller must have allowance for ``from``'s tokens of at least
    140
       * `value`.
    141
       */
    142 28×
      function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
    143
        address spender = _msgSender();
    144 12×
        _spendAllowance(from, spender, value);
    145 14×
        _transfer(from, to, value);
    146
        return true;
    147
      }
    148
    149
      /**
    150
       * @dev Moves a `value` amount of tokens from `from` to `to`.
    151
       *
    152
       * This internal function is equivalent to {transfer}, and can be used to
    153
       * e.g. implement automatic token fees, slashing mechanisms, etc.
    154
       *
    155
       * Emits a {Transfer} event.
    156
       *
    157
       * NOTE: This function is not virtual, {_update} should be overridden instead.
    158
       */
    159
      function _transfer(address from, address to, uint256 value) internal {
    160 15×
        if (from == address(0)) {
    161 0
          revert ERC20InvalidSender(address(0));
    162
        }
    163 15×
        if (to == address(0)) {
    164 0
          revert ERC20InvalidReceiver(address(0));
    165
        }
    166 18×
        _update(from, to, value);
    167
      }
    168
    169
      /**
    170
       * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
    171
       * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
    172
       * this function.
    173
       *
    174
       * Emits a {Transfer} event.
    175
       */
    176 15×
      function _update(address from, address to, uint256 value) internal virtual {
    177 18×
        if (from == address(0)) {
    178
          // Overflow check required: The rest of the code assumes that totalSupply never overflows
    179 0
          _totalSupply += value;
    180
        } else {
    181 42×
          uint256 fromBalance = _balances[from];
    182 21×
          if (fromBalance < value) {
    183 0
            revert ERC20InsufficientBalance(from, fromBalance, value);
    184
          }
    185
          unchecked {
    186
            // Overflow not possible: value <= fromBalance <= totalSupply.
    187 57×
            _balances[from] = fromBalance - value;
    188
          }
    189
        }
    190
    191 18×
        if (to == address(0)) {
    192
          unchecked {
    193
            // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
    194 0
            _totalSupply -= value;
    195
          }
    196
        } else {
    197
          unchecked {
    198
            // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
    199 57×
            _balances[to] += value;
    200
          }
    201
        }
    202
    203 54×
        emit Transfer(from, to, value);
    204
      }
    205
    206
      /**
    207
       * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
    208
       * Relies on the `_update` mechanism
    209
       *
    210
       * Emits a {Transfer} event with `from` set to the zero address.
    211
       *
    212
       * NOTE: This function is not virtual, {_update} should be overridden instead.
    213
       */
    214 0
      function _mint(address account, uint256 value) internal {
    215 0
        if (account == address(0)) {
    216 0
          revert ERC20InvalidReceiver(address(0));
    217
        }
    218 0
        _update(address(0), account, value);
    219
      }
    220
    221
      /**
    222
       * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
    223
       * Relies on the `_update` mechanism.
    224
       *
    225
       * Emits a {Transfer} event with `to` set to the zero address.
    226
       *
    227
       * NOTE: This function is not virtual, {_update} should be overridden instead
    228
       */
    229 0
      function _burn(address account, uint256 value) internal {
    230 0
        if (account == address(0)) {
    231 0
          revert ERC20InvalidSender(address(0));
    232
        }
    233 0
        _update(account, address(0), value);
    234
      }
    235
    236
      /**
    237
       * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
    238
       *
    239
       * This internal function is equivalent to `approve`, and can be used to
    240
       * e.g. set automatic allowances for certain subsystems, etc.
    241
       *
    242
       * Emits an {Approval} event.
    243
       *
    244
       * Requirements:
    245
       *
    246
       * - `owner` cannot be the zero address.
    247
       * - `spender` cannot be the zero address.
    248
       *
    249
       * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
    250
       */
    251 12×
      function _approve(address owner, address spender, uint256 value) internal {
    252
        _approve(owner, spender, value, true);
    253
      }
    254
    255
      /**
    256
       * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
    257
       *
    258
       * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
    259
       * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
    260
       * `Approval` event during `transferFrom` operations.
    261
       *
    262
       * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
    263
       * true using the following override:
    264
       *
    265
       * ```solidity
    266
       * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
    267
       *     super._approve(owner, spender, value, true);
    268
       * }
    269
       * ```
    270
       *
    271
       * Requirements are the same as {_approve}.
    272
       */
    273
      function _approve(
    274
        address owner,
    275
        address spender,
    276
        uint256 value,
    277
        bool emitEvent
    278
      ) internal virtual {
    279
        if (owner == address(0)) {
    280 0
          revert ERC20InvalidApprover(address(0));
    281
        }
    282
        if (spender == address(0)) {
    283 0
          revert ERC20InvalidSpender(address(0));
    284
        }
    285 28×
        _allowances[owner][spender] = value;
    286
        if (emitEvent) {
    287 0
          emit Approval(owner, spender, value);
    288
        }
    289
      }
    290
    291
      /**
    292
       * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
    293
       *
    294
       * Does not update the allowance value in case of infinite allowance.
    295
       * Revert if not enough allowance is available.
    296
       *
    297
       * Does not emit an {Approval} event.
    298
       */
    299 12×
      function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
    300 18×
        uint256 currentAllowance = allowance(owner, spender);
    301 10×
        if (currentAllowance < type(uint256).max) {
    302
          if (currentAllowance < value) {
    303 16×
            revert ERC20InsufficientAllowance(spender, currentAllowance, value);
    304
          }
    305
          unchecked {
    306 11×
            _approve(owner, spender, currentAllowance - value, false);
    307
          }
    308
        }
    309
      }
    310
    }
    34% src/dependencies/openzeppelin/EnumerableSet.sol
    Lines covered: 19 / 55 (34%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/structs/EnumerableSet.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
    4
    5
    pragma solidity ^0.8.20;
    6
    7
    import {Arrays} from './Arrays.sol';
    8
    import {Math} from './Math.sol';
    9
    10
    /**
    11
     * @dev Library for managing
    12
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
    13
     * types.
    14
     *
    15
     * Sets have the following properties:
    16
     *
    17
     * - Elements are added, removed, and checked for existence in constant time
    18
     * (O(1)).
    19
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
    20
     * - Set can be cleared (all elements removed) in O(n).
    21
     *
    22
     * ```solidity
    23
     * contract Example {
    24
     *     // Add the library methods
    25
     *     using EnumerableSet for EnumerableSet.AddressSet;
    26
     *
    27
     *     // Declare a set state variable
    28
     *     EnumerableSet.AddressSet private mySet;
    29
     * }
    30
     * ```
    31
     *
    32
     * The following types are supported:
    33
     *
    34
     * - `bytes32` (`Bytes32Set`) since v3.3.0
    35
     * - `address` (`AddressSet`) since v3.3.0
    36
     * - `uint256` (`UintSet`) since v3.3.0
    37
     * - `string` (`StringSet`) since v5.4.0
    38
     * - `bytes` (`BytesSet`) since v5.4.0
    39
     *
    40
     * [WARNING]
    41
     * ====
    42
     * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
    43
     * unusable.
    44
     * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
    45
     *
    46
     * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
    47
     * array of EnumerableSet.
    48
     * ====
    49
     */
    50 0
    library EnumerableSet {
    51
      // To implement this library for multiple types with as little code
    52
      // repetition as possible, we write it in terms of a generic Set type with
    53
      // bytes32 values.
    54
      // The Set implementation uses private functions, and user-facing
    55
      // implementations (such as AddressSet) are just wrappers around the
    56
      // underlying Set.
    57
      // This means that we can only create new EnumerableSets for types that fit
    58
      // in bytes32.
    59
    60
      struct Set {
    61
        // Storage of set values
    62
        bytes32[] _values;
    63
        // Position is the index of the value in the `values` array plus 1.
    64
        // Position 0 is used to mean a value is not in the set.
    65
        mapping(bytes32 value => uint256) _positions;
    66
      }
    67
    68
      /**
    69
       * @dev Add a value to a set. O(1).
    70
       *
    71
       * Returns true if the value was added to the set, that is if it was not
    72
       * already present.
    73
       */
    74
      function _add(Set storage set, bytes32 value) private returns (bool) {
    75
        if (!_contains(set, value)) {
    76 22×
          set._values.push(value);
    77
          // The value is stored at length-1, but we add 1 to all indexes
    78
          // and use 0 as a sentinel value
    79 18×
          set._positions[value] = set._values.length;
    80
          return true;
    81
        } else {
    82 0
          return false;
    83
        }
    84
      }
    85
    86
      /**
    87
       * @dev Removes a value from a set. O(1).
    88
       *
    89
       * Returns true if the value was removed from the set, that is if it was
    90
       * present.
    91
       */
    92 0
      function _remove(Set storage set, bytes32 value) private returns (bool) {
    93
        // We cache the value's position to prevent multiple reads from the same storage slot
    94 0
        uint256 position = set._positions[value];
    95
    96 0
        if (position != 0) {
    97
          // Equivalent to contains(set, value)
    98
          // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    99
          // the array, and then remove the last element (sometimes called as 'swap and pop').
    100
          // This modifies the order of the array, as noted in {at}.
    101
    102 0
          uint256 valueIndex = position - 1;
    103 0
          uint256 lastIndex = set._values.length - 1;
    104
    105 0
          if (valueIndex != lastIndex) {
    106 0
            bytes32 lastValue = set._values[lastIndex];
    107
    108
            // Move the lastValue to the index where the value to delete is
    109 0
            set._values[valueIndex] = lastValue;
    110
            // Update the tracked position of the lastValue (that was just moved)
    111 0
            set._positions[lastValue] = position;
    112
          }
    113
    114
          // Delete the slot where the moved value was stored
    115 0
          set._values.pop();
    116
    117
          // Delete the tracked position for the deleted slot
    118 0
          delete set._positions[value];
    119
    120 0
          return true;
    121
        } else {
    122 0
          return false;
    123
        }
    124
      }
    125
    126
      /**
    127
       * @dev Removes all the values from a set. O(n).
    128
       *
    129
       * WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that
    130
       * using it may render the function uncallable if the set grows to the point where clearing it consumes too much
    131
       * gas to fit in a block.
    132
       */
    133
      function _clear(Set storage set) private {
    134
        uint256 len = _length(set);
    135
        for (uint256 i = 0; i < len; ++i) {
    136
          delete set._positions[set._values[i]];
    137
        }
    138
        Arrays.unsafeSetLength(set._values, 0);
    139
      }
    140
    141
      /**
    142
       * @dev Returns true if the value is in the set. O(1).
    143
       */
    144
      function _contains(Set storage set, bytes32 value) private view returns (bool) {
    145 26×
        return set._positions[value] != 0;
    146
      }
    147
    148
      /**
    149
       * @dev Returns the number of values on the set. O(1).
    150
       */
    151
      function _length(Set storage set) private view returns (uint256) {
    152
        return set._values.length;
    153
      }
    154
    155
      /**
    156
       * @dev Returns the value stored at position `index` in the set. O(1).
    157
       *
    158
       * Note that there are no guarantees on the ordering of values inside the
    159
       * array, and it may change when more values are added or removed.
    160
       *
    161
       * Requirements:
    162
       *
    163
       * - `index` must be strictly less than {length}.
    164
       */
    165 14×
      function _at(Set storage set, uint256 index) private view returns (bytes32) {
    166 42×
        return set._values[index];
    167
      }
    168
    169
      /**
    170
       * @dev Return the entire set in an array
    171
       *
    172
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    173
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    174
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    175
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    176
       */
    177
      function _values(Set storage set) private view returns (bytes32[] memory) {
    178
        return set._values;
    179
      }
    180
    181
      /**
    182
       * @dev Return a slice of the set in an array
    183
       *
    184
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    185
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    186
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    187
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    188
       */
    189 0
      function _values(
    190
        Set storage set,
    191
        uint256 start,
    192
        uint256 end
    193 0
      ) private view returns (bytes32[] memory) {
    194
        unchecked {
    195 0
          end = Math.min(end, _length(set));
    196 0
          start = Math.min(start, end);
    197
    198 0
          uint256 len = end - start;
    199 0
          bytes32[] memory result = new bytes32[](len);
    200 0
          for (uint256 i = 0; i < len; ++i) {
    201 0
            result[i] = Arrays.unsafeAccess(set._values, start + i).value;
    202
          }
    203
          return result;
    204
        }
    205
      }
    206
    207
      // Bytes32Set
    208
    209
      struct Bytes32Set {
    210
        Set _inner;
    211
      }
    212
    213
      /**
    214
       * @dev Add a value to a set. O(1).
    215
       *
    216
       * Returns true if the value was added to the set, that is if it was not
    217
       * already present.
    218
       */
    219 0
      function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    220 0
        return _add(set._inner, value);
    221
      }
    222
    223
      /**
    224
       * @dev Removes a value from a set. O(1).
    225
       *
    226
       * Returns true if the value was removed from the set, that is if it was
    227
       * present.
    228
       */
    229 0
      function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    230 0
        return _remove(set._inner, value);
    231
      }
    232
    233
      /**
    234
       * @dev Removes all the values from a set. O(n).
    235
       *
    236
       * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    237
       * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    238
       */
    239
      function clear(Bytes32Set storage set) internal {
    240
        _clear(set._inner);
    241
      }
    242
    243
      /**
    244
       * @dev Returns true if the value is in the set. O(1).
    245
       */
    246
      function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
    247
        return _contains(set._inner, value);
    248
      }
    249
    250
      /**
    251
       * @dev Returns the number of values in the set. O(1).
    252
       */
    253 0
      function length(Bytes32Set storage set) internal view returns (uint256) {
    254 0
        return _length(set._inner);
    255
      }
    256
    257
      /**
    258
       * @dev Returns the value stored at position `index` in the set. O(1).
    259
       *
    260
       * Note that there are no guarantees on the ordering of values inside the
    261
       * array, and it may change when more values are added or removed.
    262
       *
    263
       * Requirements:
    264
       *
    265
       * - `index` must be strictly less than {length}.
    266
       */
    267 0
      function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
    268 0
        return _at(set._inner, index);
    269
      }
    270
    271
      /**
    272
       * @dev Return the entire set in an array
    273
       *
    274
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    275
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    276
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    277
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    278
       */
    279
      function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
    280
        bytes32[] memory store = _values(set._inner);
    281
        bytes32[] memory result;
    282
    283
        assembly ('memory-safe') {
    284
          result := store
    285
        }
    286
    287
        return result;
    288
      }
    289
    290
      /**
    291
       * @dev Return a slice of the set in an array
    292
       *
    293
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    294
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    295
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    296
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    297
       */
    298 0
      function values(
    299
        Bytes32Set storage set,
    300
        uint256 start,
    301
        uint256 end
    302 0
      ) internal view returns (bytes32[] memory) {
    303 0
        bytes32[] memory store = _values(set._inner, start, end);
    304
        bytes32[] memory result;
    305
    306
        assembly ('memory-safe') {
    307
          result := store
    308
        }
    309
    310
        return result;
    311
      }
    312
    313
      // AddressSet
    314
    315
      struct AddressSet {
    316
        Set _inner;
    317
      }
    318
    319
      /**
    320
       * @dev Add a value to a set. O(1).
    321
       *
    322
       * Returns true if the value was added to the set, that is if it was not
    323
       * already present.
    324
       */
    325
      function add(AddressSet storage set, address value) internal returns (bool) {
    326
        return _add(set._inner, bytes32(uint256(uint160(value))));
    327
      }
    328
    329
      /**
    330
       * @dev Removes a value from a set. O(1).
    331
       *
    332
       * Returns true if the value was removed from the set, that is if it was
    333
       * present.
    334
       */
    335 0
      function remove(AddressSet storage set, address value) internal returns (bool) {
    336 0
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    337
      }
    338
    339
      /**
    340
       * @dev Removes all the values from a set. O(n).
    341
       *
    342
       * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    343
       * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    344
       */
    345
      function clear(AddressSet storage set) internal {
    346
        _clear(set._inner);
    347
      }
    348
    349
      /**
    350
       * @dev Returns true if the value is in the set. O(1).
    351
       */
    352
      function contains(AddressSet storage set, address value) internal view returns (bool) {
    353
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    354
      }
    355
    356
      /**
    357
       * @dev Returns the number of values in the set. O(1).
    358
       */
    359
      function length(AddressSet storage set) internal view returns (uint256) {
    360
        return _length(set._inner);
    361
      }
    362
    363
      /**
    364
       * @dev Returns the value stored at position `index` in the set. O(1).
    365
       *
    366
       * Note that there are no guarantees on the ordering of values inside the
    367
       * array, and it may change when more values are added or removed.
    368
       *
    369
       * Requirements:
    370
       *
    371
       * - `index` must be strictly less than {length}.
    372
       */
    373
      function at(AddressSet storage set, uint256 index) internal view returns (address) {
    374 10×
        return address(uint160(uint256(_at(set._inner, index))));
    375
      }
    376
    377
      /**
    378
       * @dev Return the entire set in an array
    379
       *
    380
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    381
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    382
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    383
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    384
       */
    385
      function values(AddressSet storage set) internal view returns (address[] memory) {
    386
        bytes32[] memory store = _values(set._inner);
    387
        address[] memory result;
    388
    389
        assembly ('memory-safe') {
    390
          result := store
    391
        }
    392
    393
        return result;
    394
      }
    395
    396
      /**
    397
       * @dev Return a slice of the set in an array
    398
       *
    399
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    400
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    401
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    402
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    403
       */
    404
      function values(
    405
        AddressSet storage set,
    406
        uint256 start,
    407
        uint256 end
    408
      ) internal view returns (address[] memory) {
    409
        bytes32[] memory store = _values(set._inner, start, end);
    410
        address[] memory result;
    411
    412
        assembly ('memory-safe') {
    413
          result := store
    414
        }
    415
    416
        return result;
    417
      }
    418
    419
      // UintSet
    420
    421
      struct UintSet {
    422
        Set _inner;
    423
      }
    424
    425
      /**
    426
       * @dev Add a value to a set. O(1).
    427
       *
    428
       * Returns true if the value was added to the set, that is if it was not
    429
       * already present.
    430
       */
    431
      function add(UintSet storage set, uint256 value) internal returns (bool) {
    432
        return _add(set._inner, bytes32(value));
    433
      }
    434
    435
      /**
    436
       * @dev Removes a value from a set. O(1).
    437
       *
    438
       * Returns true if the value was removed from the set, that is if it was
    439
       * present.
    440
       */
    441
      function remove(UintSet storage set, uint256 value) internal returns (bool) {
    442
        return _remove(set._inner, bytes32(value));
    443
      }
    444
    445
      /**
    446
       * @dev Removes all the values from a set. O(n).
    447
       *
    448
       * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    449
       * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    450
       */
    451
      function clear(UintSet storage set) internal {
    452
        _clear(set._inner);
    453
      }
    454
    455
      /**
    456
       * @dev Returns true if the value is in the set. O(1).
    457
       */
    458
      function contains(UintSet storage set, uint256 value) internal view returns (bool) {
    459
        return _contains(set._inner, bytes32(value));
    460
      }
    461
    462
      /**
    463
       * @dev Returns the number of values in the set. O(1).
    464
       */
    465
      function length(UintSet storage set) internal view returns (uint256) {
    466
        return _length(set._inner);
    467
      }
    468
    469
      /**
    470
       * @dev Returns the value stored at position `index` in the set. O(1).
    471
       *
    472
       * Note that there are no guarantees on the ordering of values inside the
    473
       * array, and it may change when more values are added or removed.
    474
       *
    475
       * Requirements:
    476
       *
    477
       * - `index` must be strictly less than {length}.
    478
       */
    479
      function at(UintSet storage set, uint256 index) internal view returns (uint256) {
    480
        return uint256(_at(set._inner, index));
    481
      }
    482
    483
      /**
    484
       * @dev Return the entire set in an array
    485
       *
    486
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    487
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    488
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    489
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    490
       */
    491
      function values(UintSet storage set) internal view returns (uint256[] memory) {
    492
        bytes32[] memory store = _values(set._inner);
    493
        uint256[] memory result;
    494
    495
        assembly ('memory-safe') {
    496
          result := store
    497
        }
    498
    499
        return result;
    500
      }
    501
    502
      /**
    503
       * @dev Return a slice of the set in an array
    504
       *
    505
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    506
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    507
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    508
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    509
       */
    510
      function values(
    511
        UintSet storage set,
    512
        uint256 start,
    513
        uint256 end
    514
      ) internal view returns (uint256[] memory) {
    515
        bytes32[] memory store = _values(set._inner, start, end);
    516
        uint256[] memory result;
    517
    518
        assembly ('memory-safe') {
    519
          result := store
    520
        }
    521
    522
        return result;
    523
      }
    524
    525
      struct StringSet {
    526
        // Storage of set values
    527
        string[] _values;
    528
        // Position is the index of the value in the `values` array plus 1.
    529
        // Position 0 is used to mean a value is not in the set.
    530
        mapping(string value => uint256) _positions;
    531
      }
    532
    533
      /**
    534
       * @dev Add a value to a set. O(1).
    535
       *
    536
       * Returns true if the value was added to the set, that is if it was not
    537
       * already present.
    538
       */
    539
      function add(StringSet storage set, string memory value) internal returns (bool) {
    540
        if (!contains(set, value)) {
    541
          set._values.push(value);
    542
          // The value is stored at length-1, but we add 1 to all indexes
    543
          // and use 0 as a sentinel value
    544
          set._positions[value] = set._values.length;
    545
          return true;
    546
        } else {
    547
          return false;
    548
        }
    549
      }
    550
    551
      /**
    552
       * @dev Removes a value from a set. O(1).
    553
       *
    554
       * Returns true if the value was removed from the set, that is if it was
    555
       * present.
    556
       */
    557
      function remove(StringSet storage set, string memory value) internal returns (bool) {
    558
        // We cache the value's position to prevent multiple reads from the same storage slot
    559
        uint256 position = set._positions[value];
    560
    561
        if (position != 0) {
    562
          // Equivalent to contains(set, value)
    563
          // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    564
          // the array, and then remove the last element (sometimes called as 'swap and pop').
    565
          // This modifies the order of the array, as noted in {at}.
    566
    567
          uint256 valueIndex = position - 1;
    568
          uint256 lastIndex = set._values.length - 1;
    569
    570
          if (valueIndex != lastIndex) {
    571
            string memory lastValue = set._values[lastIndex];
    572
    573
            // Move the lastValue to the index where the value to delete is
    574
            set._values[valueIndex] = lastValue;
    575
            // Update the tracked position of the lastValue (that was just moved)
    576
            set._positions[lastValue] = position;
    577
          }
    578
    579
          // Delete the slot where the moved value was stored
    580
          set._values.pop();
    581
    582
          // Delete the tracked position for the deleted slot
    583
          delete set._positions[value];
    584
    585
          return true;
    586
        } else {
    587
          return false;
    588
        }
    589
      }
    590
    591
      /**
    592
       * @dev Removes all the values from a set. O(n).
    593
       *
    594
       * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    595
       * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    596
       */
    597
      function clear(StringSet storage set) internal {
    598
        uint256 len = length(set);
    599
        for (uint256 i = 0; i < len; ++i) {
    600
          delete set._positions[set._values[i]];
    601
        }
    602
        Arrays.unsafeSetLength(set._values, 0);
    603
      }
    604
    605
      /**
    606
       * @dev Returns true if the value is in the set. O(1).
    607
       */
    608
      function contains(StringSet storage set, string memory value) internal view returns (bool) {
    609
        return set._positions[value] != 0;
    610
      }
    611
    612
      /**
    613
       * @dev Returns the number of values on the set. O(1).
    614
       */
    615
      function length(StringSet storage set) internal view returns (uint256) {
    616
        return set._values.length;
    617
      }
    618
    619
      /**
    620
       * @dev Returns the value stored at position `index` in the set. O(1).
    621
       *
    622
       * Note that there are no guarantees on the ordering of values inside the
    623
       * array, and it may change when more values are added or removed.
    624
       *
    625
       * Requirements:
    626
       *
    627
       * - `index` must be strictly less than {length}.
    628
       */
    629
      function at(StringSet storage set, uint256 index) internal view returns (string memory) {
    630
        return set._values[index];
    631
      }
    632
    633
      /**
    634
       * @dev Return the entire set in an array
    635
       *
    636
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    637
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    638
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    639
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    640
       */
    641
      function values(StringSet storage set) internal view returns (string[] memory) {
    642
        return set._values;
    643
      }
    644
    645
      /**
    646
       * @dev Return a slice of the set in an array
    647
       *
    648
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    649
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    650
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    651
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    652
       */
    653
      function values(
    654
        StringSet storage set,
    655
        uint256 start,
    656
        uint256 end
    657
      ) internal view returns (string[] memory) {
    658
        unchecked {
    659
          end = Math.min(end, length(set));
    660
          start = Math.min(start, end);
    661
    662
          uint256 len = end - start;
    663
          string[] memory result = new string[](len);
    664
          for (uint256 i = 0; i < len; ++i) {
    665
            result[i] = Arrays.unsafeAccess(set._values, start + i).value;
    666
          }
    667
          return result;
    668
        }
    669
      }
    670
    671
      struct BytesSet {
    672
        // Storage of set values
    673
        bytes[] _values;
    674
        // Position is the index of the value in the `values` array plus 1.
    675
        // Position 0 is used to mean a value is not in the set.
    676
        mapping(bytes value => uint256) _positions;
    677
      }
    678
    679
      /**
    680
       * @dev Add a value to a set. O(1).
    681
       *
    682
       * Returns true if the value was added to the set, that is if it was not
    683
       * already present.
    684
       */
    685
      function add(BytesSet storage set, bytes memory value) internal returns (bool) {
    686
        if (!contains(set, value)) {
    687
          set._values.push(value);
    688
          // The value is stored at length-1, but we add 1 to all indexes
    689
          // and use 0 as a sentinel value
    690
          set._positions[value] = set._values.length;
    691
          return true;
    692
        } else {
    693
          return false;
    694
        }
    695
      }
    696
    697
      /**
    698
       * @dev Removes a value from a set. O(1).
    699
       *
    700
       * Returns true if the value was removed from the set, that is if it was
    701
       * present.
    702
       */
    703
      function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
    704
        // We cache the value's position to prevent multiple reads from the same storage slot
    705
        uint256 position = set._positions[value];
    706
    707
        if (position != 0) {
    708
          // Equivalent to contains(set, value)
    709
          // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    710
          // the array, and then remove the last element (sometimes called as 'swap and pop').
    711
          // This modifies the order of the array, as noted in {at}.
    712
    713
          uint256 valueIndex = position - 1;
    714
          uint256 lastIndex = set._values.length - 1;
    715
    716
          if (valueIndex != lastIndex) {
    717
            bytes memory lastValue = set._values[lastIndex];
    718
    719
            // Move the lastValue to the index where the value to delete is
    720
            set._values[valueIndex] = lastValue;
    721
            // Update the tracked position of the lastValue (that was just moved)
    722
            set._positions[lastValue] = position;
    723
          }
    724
    725
          // Delete the slot where the moved value was stored
    726
          set._values.pop();
    727
    728
          // Delete the tracked position for the deleted slot
    729
          delete set._positions[value];
    730
    731
          return true;
    732
        } else {
    733
          return false;
    734
        }
    735
      }
    736
    737
      /**
    738
       * @dev Removes all the values from a set. O(n).
    739
       *
    740
       * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    741
       * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    742
       */
    743
      function clear(BytesSet storage set) internal {
    744
        uint256 len = length(set);
    745
        for (uint256 i = 0; i < len; ++i) {
    746
          delete set._positions[set._values[i]];
    747
        }
    748
        Arrays.unsafeSetLength(set._values, 0);
    749
      }
    750
    751
      /**
    752
       * @dev Returns true if the value is in the set. O(1).
    753
       */
    754
      function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
    755
        return set._positions[value] != 0;
    756
      }
    757
    758
      /**
    759
       * @dev Returns the number of values on the set. O(1).
    760
       */
    761
      function length(BytesSet storage set) internal view returns (uint256) {
    762
        return set._values.length;
    763
      }
    764
    765
      /**
    766
       * @dev Returns the value stored at position `index` in the set. O(1).
    767
       *
    768
       * Note that there are no guarantees on the ordering of values inside the
    769
       * array, and it may change when more values are added or removed.
    770
       *
    771
       * Requirements:
    772
       *
    773
       * - `index` must be strictly less than {length}.
    774
       */
    775
      function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
    776
        return set._values[index];
    777
      }
    778
    779
      /**
    780
       * @dev Return the entire set in an array
    781
       *
    782
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    783
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    784
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    785
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    786
       */
    787
      function values(BytesSet storage set) internal view returns (bytes[] memory) {
    788
        return set._values;
    789
      }
    790
    791
      /**
    792
       * @dev Return a slice of the set in an array
    793
       *
    794
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    795
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    796
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    797
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    798
       */
    799
      function values(
    800
        BytesSet storage set,
    801
        uint256 start,
    802
        uint256 end
    803
      ) internal view returns (bytes[] memory) {
    804
        unchecked {
    805
          end = Math.min(end, length(set));
    806
          start = Math.min(start, end);
    807
    808
          uint256 len = end - start;
    809
          bytes[] memory result = new bytes[](len);
    810
          for (uint256 i = 0; i < len; ++i) {
    811
            result[i] = Arrays.unsafeAccess(set._values, start + i).value;
    812
          }
    813
          return result;
    814
        }
    815
      }
    816
    }
    0% src/dependencies/openzeppelin/Errors.sol
    Lines covered: 0 / 1 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Collection of common custom errors used in multiple contracts
    8
     *
    9
     * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
    10
     * It is recommended to avoid relying on the error API for critical functionality.
    11
     *
    12
     * _Available since v5.1._
    13
     */
    14 0
    library Errors {
    15
      /**
    16
       * @dev The ETH balance of the account is not enough to perform the operation.
    17
       */
    18
      error InsufficientBalance(uint256 balance, uint256 needed);
    19
    20
      /**
    21
       * @dev A call to an address target failed. The target may have reverted.
    22
       */
    23
      error FailedCall();
    24
    25
      /**
    26
       * @dev The deployment failed.
    27
       */
    28
      error FailedDeployment();
    29
    30
      /**
    31
       * @dev A necessary precompile is missing.
    32
       */
    33
      error MissingPrecompile(address);
    34
    }
    0% src/dependencies/openzeppelin/Math.sol
    Lines covered: 0 / 42 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Panic} from './Panic.sol';
    7
    import {SafeCast} from './SafeCast.sol';
    8
    9
    /**
    10
     * @dev Standard math utilities missing in the Solidity language.
    11
     */
    12 0
    library Math {
    13
      enum Rounding {
    14
        Floor, // Toward negative infinity
    15
        Ceil, // Toward positive infinity
    16
        Trunc, // Toward zero
    17
        Expand // Away from zero
    18
      }
    19
    20
      /**
    21
       * @dev Return the 512-bit addition of two uint256.
    22
       *
    23
       * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
    24
       */
    25
      function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
    26
        assembly ('memory-safe') {
    27
          low := add(a, b)
    28
          high := lt(low, a)
    29
        }
    30
      }
    31
    32
      /**
    33
       * @dev Return the 512-bit multiplication of two uint256.
    34
       *
    35
       * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
    36
       */
    37 0
      function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
    38
        // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
    39
        // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
    40
        // variables such that product = high * 2²⁵⁶ + low.
    41
        assembly ('memory-safe') {
    42 0
          let mm := mulmod(a, b, not(0))
    43 0
          low := mul(a, b)
    44 0
          high := sub(sub(mm, low), lt(mm, low))
    45
        }
    46
      }
    47
    48
      /**
    49
       * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
    50
       */
    51
      function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    52
        unchecked {
    53
          uint256 c = a + b;
    54
          success = c >= a;
    55
          result = c * SafeCast.toUint(success);
    56
        }
    57
      }
    58
    59
      /**
    60
       * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
    61
       */
    62
      function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    63
        unchecked {
    64
          uint256 c = a - b;
    65
          success = c <= a;
    66
          result = c * SafeCast.toUint(success);
    67
        }
    68
      }
    69
    70
      /**
    71
       * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
    72
       */
    73
      function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    74
        unchecked {
    75
          uint256 c = a * b;
    76
          assembly ('memory-safe') {
    77
            // Only true when the multiplication doesn't overflow
    78
            // (c / a == b) || (a == 0)
    79
            success := or(eq(div(c, a), b), iszero(a))
    80
          }
    81
          // equivalent to: success ? c : 0
    82
          result = c * SafeCast.toUint(success);
    83
        }
    84
      }
    85
    86
      /**
    87
       * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
    88
       */
    89
      function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    90
        unchecked {
    91
          success = b > 0;
    92
          assembly ('memory-safe') {
    93
            // The `DIV` opcode returns zero when the denominator is 0.
    94
            result := div(a, b)
    95
          }
    96
        }
    97
      }
    98
    99
      /**
    100
       * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
    101
       */
    102
      function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
    103
        unchecked {
    104
          success = b > 0;
    105
          assembly ('memory-safe') {
    106
            // The `MOD` opcode returns zero when the denominator is 0.
    107
            result := mod(a, b)
    108
          }
    109
        }
    110
      }
    111
    112
      /**
    113
       * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
    114
       */
    115
      function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
    116
        (bool success, uint256 result) = tryAdd(a, b);
    117
        return ternary(success, result, type(uint256).max);
    118
      }
    119
    120
      /**
    121
       * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
    122
       */
    123
      function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
    124
        (, uint256 result) = trySub(a, b);
    125
        return result;
    126
      }
    127
    128
      /**
    129
       * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
    130
       */
    131
      function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
    132
        (bool success, uint256 result) = tryMul(a, b);
    133
        return ternary(success, result, type(uint256).max);
    134
      }
    135
    136
      /**
    137
       * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
    138
       *
    139
       * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
    140
       * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
    141
       * one branch when needed, making this function more expensive.
    142
       */
    143 0
      function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
    144
        unchecked {
    145
          // branchless ternary works because:
    146
          // b ^ (a ^ b) == a
    147
          // b ^ 0 == b
    148 0
          return b ^ ((a ^ b) * SafeCast.toUint(condition));
    149
        }
    150
      }
    151
    152
      /**
    153
       * @dev Returns the largest of two numbers.
    154
       */
    155 0
      function max(uint256 a, uint256 b) internal pure returns (uint256) {
    156 0
        return ternary(a > b, a, b);
    157
      }
    158
    159
      /**
    160
       * @dev Returns the smallest of two numbers.
    161
       */
    162 0
      function min(uint256 a, uint256 b) internal pure returns (uint256) {
    163 0
        return ternary(a < b, a, b);
    164
      }
    165
    166
      /**
    167
       * @dev Returns the average of two numbers. The result is rounded towards
    168
       * zero.
    169
       */
    170
      function average(uint256 a, uint256 b) internal pure returns (uint256) {
    171
        // (a + b) / 2 can overflow.
    172
        return (a & b) + (a ^ b) / 2;
    173
      }
    174
    175
      /**
    176
       * @dev Returns the ceiling of the division of two numbers.
    177
       *
    178
       * This differs from standard division with `/` in that it rounds towards infinity instead
    179
       * of rounding towards zero.
    180
       */
    181
      function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
    182
        if (b == 0) {
    183
          // Guarantee the same behavior as in a regular Solidity division.
    184
          Panic.panic(Panic.DIVISION_BY_ZERO);
    185
        }
    186
    187
        // The following calculation ensures accurate ceiling division without overflow.
    188
        // Since a is non-zero, (a - 1) / b will not overflow.
    189
        // The largest possible result occurs when (a - 1) / b is type(uint256).max,
    190
        // but the largest value we can obtain is type(uint256).max - 1, which happens
    191
        // when a = type(uint256).max and b = 1.
    192
        unchecked {
    193
          return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
    194
        }
    195
      }
    196
    197
      /**
    198
       * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
    199
       * denominator == 0.
    200
       *
    201
       * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
    202
       * Uniswap Labs also under MIT license.
    203
       */
    204 0
      function mulDiv(
    205
        uint256 x,
    206
        uint256 y,
    207
        uint256 denominator
    208 0
      ) internal pure returns (uint256 result) {
    209
        unchecked {
    210 0
          (uint256 high, uint256 low) = mul512(x, y);
    211
    212
          // Handle non-overflow cases, 256 by 256 division.
    213 0
          if (high == 0) {
    214
            // Solidity will revert if denominator == 0, unlike the div opcode on its own.
    215
            // The surrounding unchecked block does not change this fact.
    216
            // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
    217 0
            return low / denominator;
    218
          }
    219
    220
          // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
    221 0
          if (denominator <= high) {
    222 0
            Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
    223
          }
    224
    225
          ///////////////////////////////////////////////
    226
          // 512 by 256 division.
    227
          ///////////////////////////////////////////////
    228
    229
          // Make division exact by subtracting the remainder from [high low].
    230 0
          uint256 remainder;
    231
          assembly ('memory-safe') {
    232
            // Compute remainder using mulmod.
    233 0
            remainder := mulmod(x, y, denominator)
    234
    235
            // Subtract 256 bit number from 512 bit number.
    236 0
            high := sub(high, gt(remainder, low))
    237 0
            low := sub(low, remainder)
    238
          }
    239
    240
          // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
    241
          // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
    242
    243 0
          uint256 twos = denominator & (0 - denominator);
    244
          assembly ('memory-safe') {
    245
            // Divide denominator by twos.
    246 0
            denominator := div(denominator, twos)
    247
    248
            // Divide [high low] by twos.
    249 0
            low := div(low, twos)
    250
    251
            // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
    252 0
            twos := add(div(sub(0, twos), twos), 1)
    253
          }
    254
    255
          // Shift in bits from high into low.
    256 0
          low |= high * twos;
    257
    258
          // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
    259
          // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
    260
          // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
    261 0
          uint256 inverse = (3 * denominator) ^ 2;
    262
    263
          // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
    264
          // works in modular arithmetic, doubling the correct bits in each step.
    265 0
          inverse *= 2 - denominator * inverse; // inverse mod 2⁸
    266 0
          inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
    267 0
          inverse *= 2 - denominator * inverse; // inverse mod 2³²
    268 0
          inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
    269 0
          inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
    270 0
          inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
    271
    272
          // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
    273
          // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
    274
          // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
    275
          // is no longer required.
    276 0
          result = low * inverse;
    277
          return result;
    278
        }
    279
      }
    280
    281
      /**
    282
       * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
    283
       */
    284 0
      function mulDiv(
    285
        uint256 x,
    286
        uint256 y,
    287
        uint256 denominator,
    288
        Rounding rounding
    289 0
      ) internal pure returns (uint256) {
    290 0
        return
    291 0
          mulDiv(x, y, denominator) +
    292 0
          SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    293
      }
    294
    295
      /**
    296
       * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
    297
       */
    298
      function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
    299
        unchecked {
    300
          (uint256 high, uint256 low) = mul512(x, y);
    301
          if (high >= 1 << n) {
    302
            Panic.panic(Panic.UNDER_OVERFLOW);
    303
          }
    304
          return (high << (256 - n)) | (low >> n);
    305
        }
    306
      }
    307
    308
      /**
    309
       * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
    310
       */
    311
      function mulShr(
    312
        uint256 x,
    313
        uint256 y,
    314
        uint8 n,
    315
        Rounding rounding
    316
      ) internal pure returns (uint256) {
    317
        return
    318
          mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
    319
      }
    320
    321
      /**
    322
       * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
    323
       *
    324
       * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
    325
       * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
    326
       *
    327
       * If the input value is not inversible, 0 is returned.
    328
       *
    329
       * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
    330
       * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
    331
       */
    332
      function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
    333
        unchecked {
    334
          if (n == 0) return 0;
    335
    336
          // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
    337
          // Used to compute integers x and y such that: ax + ny = gcd(a, n).
    338
          // When the gcd is 1, then the inverse of a modulo n exists and it's x.
    339
          // ax + ny = 1
    340
          // ax = 1 + (-y)n
    341
          // ax ≡ 1 (mod n) # x is the inverse of a modulo n
    342
    343
          // If the remainder is 0 the gcd is n right away.
    344
          uint256 remainder = a % n;
    345
          uint256 gcd = n;
    346
    347
          // Therefore the initial coefficients are:
    348
          // ax + ny = gcd(a, n) = n
    349
          // 0a + 1n = n
    350
          int256 x = 0;
    351
          int256 y = 1;
    352
    353
          while (remainder != 0) {
    354
            uint256 quotient = gcd / remainder;
    355
    356
            (gcd, remainder) = (
    357
              // The old remainder is the next gcd to try.
    358
              remainder,
    359
              // Compute the next remainder.
    360
              // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
    361
              // where gcd is at most n (capped to type(uint256).max)
    362
              gcd - remainder * quotient
    363
            );
    364
    365
            (x, y) = (
    366
              // Increment the coefficient of a.
    367
              y,
    368
              // Decrement the coefficient of n.
    369
              // Can overflow, but the result is casted to uint256 so that the
    370
              // next value of y is "wrapped around" to a value between 0 and n - 1.
    371
              x - y * int256(quotient)
    372
            );
    373
          }
    374
    375
          if (gcd != 1) return 0; // No inverse exists.
    376
          return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
    377
        }
    378
      }
    379
    380
      /**
    381
       * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
    382
       *
    383
       * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
    384
       * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
    385
       * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
    386
       *
    387
       * NOTE: this function does NOT check that `p` is a prime greater than `2`.
    388
       */
    389
      function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
    390
        unchecked {
    391
          return Math.modExp(a, p - 2, p);
    392
        }
    393
      }
    394
    395
      /**
    396
       * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
    397
       *
    398
       * Requirements:
    399
       * - modulus can't be zero
    400
       * - underlying staticcall to precompile must succeed
    401
       *
    402
       * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
    403
       * sure the chain you're using it on supports the precompiled contract for modular exponentiation
    404
       * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
    405
       * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
    406
       * interpreted as 0.
    407
       */
    408
      function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
    409
        (bool success, uint256 result) = tryModExp(b, e, m);
    410
        if (!success) {
    411
          Panic.panic(Panic.DIVISION_BY_ZERO);
    412
        }
    413
        return result;
    414
      }
    415
    416
      /**
    417
       * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
    418
       * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
    419
       * to operate modulo 0 or if the underlying precompile reverted.
    420
       *
    421
       * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
    422
       * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
    423
       * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
    424
       * of a revert, but the result may be incorrectly interpreted as 0.
    425
       */
    426
      function tryModExp(
    427
        uint256 b,
    428
        uint256 e,
    429
        uint256 m
    430
      ) internal view returns (bool success, uint256 result) {
    431
        if (m == 0) return (false, 0);
    432
        assembly ('memory-safe') {
    433
          let ptr := mload(0x40)
    434
          // | Offset    | Content    | Content (Hex)                                                      |
    435
          // |-----------|------------|--------------------------------------------------------------------|
    436
          // | 0x00:0x1f | size of b  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
    437
          // | 0x20:0x3f | size of e  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
    438
          // | 0x40:0x5f | size of m  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
    439
          // | 0x60:0x7f | value of b | 0x<.............................................................b> |
    440
          // | 0x80:0x9f | value of e | 0x<.............................................................e> |
    441
          // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
    442
          mstore(ptr, 0x20)
    443
          mstore(add(ptr, 0x20), 0x20)
    444
          mstore(add(ptr, 0x40), 0x20)
    445
          mstore(add(ptr, 0x60), b)
    446
          mstore(add(ptr, 0x80), e)
    447
          mstore(add(ptr, 0xa0), m)
    448
    449
          // Given the result < m, it's guaranteed to fit in 32 bytes,
    450
          // so we can use the memory scratch space located at offset 0.
    451
          success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
    452
          result := mload(0x00)
    453
        }
    454
      }
    455
    456
      /**
    457
       * @dev Variant of {modExp} that supports inputs of arbitrary length.
    458
       */
    459
      function modExp(
    460
        bytes memory b,
    461
        bytes memory e,
    462
        bytes memory m
    463
      ) internal view returns (bytes memory) {
    464
        (bool success, bytes memory result) = tryModExp(b, e, m);
    465
        if (!success) {
    466
          Panic.panic(Panic.DIVISION_BY_ZERO);
    467
        }
    468
        return result;
    469
      }
    470
    471
      /**
    472
       * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
    473
       */
    474
      function tryModExp(
    475
        bytes memory b,
    476
        bytes memory e,
    477
        bytes memory m
    478
      ) internal view returns (bool success, bytes memory result) {
    479
        if (_zeroBytes(m)) return (false, new bytes(0));
    480
    481
        uint256 mLen = m.length;
    482
    483
        // Encode call args in result and move the free memory pointer
    484
        result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
    485
    486
        assembly ('memory-safe') {
    487
          let dataPtr := add(result, 0x20)
    488
          // Write result on top of args to avoid allocating extra memory.
    489
          success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
    490
          // Overwrite the length.
    491
          // result.length > returndatasize() is guaranteed because returndatasize() == m.length
    492
          mstore(result, mLen)
    493
          // Set the memory pointer after the returned data.
    494
          mstore(0x40, add(dataPtr, mLen))
    495
        }
    496
      }
    497
    498
      /**
    499
       * @dev Returns whether the provided byte array is zero.
    500
       */
    501
      function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
    502
        for (uint256 i = 0; i < byteArray.length; ++i) {
    503
          if (byteArray[i] != 0) {
    504
            return false;
    505
          }
    506
        }
    507
        return true;
    508
      }
    509
    510
      /**
    511
       * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
    512
       * towards zero.
    513
       *
    514
       * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
    515
       * using integer operations.
    516
       */
    517
      function sqrt(uint256 a) internal pure returns (uint256) {
    518
        unchecked {
    519
          // Take care of easy edge cases when a == 0 or a == 1
    520
          if (a <= 1) {
    521
            return a;
    522
          }
    523
    524
          // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
    525
          // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
    526
          // the current value as `ε_n = | x_n - sqrt(a) |`.
    527
          //
    528
          // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
    529
          // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
    530
          // bigger than any uint256.
    531
          //
    532
          // By noticing that
    533
          // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
    534
          // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
    535
          // to the msb function.
    536
          uint256 aa = a;
    537
          uint256 xn = 1;
    538
    539
          if (aa >= (1 << 128)) {
    540
            aa >>= 128;
    541
            xn <<= 64;
    542
          }
    543
          if (aa >= (1 << 64)) {
    544
            aa >>= 64;
    545
            xn <<= 32;
    546
          }
    547
          if (aa >= (1 << 32)) {
    548
            aa >>= 32;
    549
            xn <<= 16;
    550
          }
    551
          if (aa >= (1 << 16)) {
    552
            aa >>= 16;
    553
            xn <<= 8;
    554
          }
    555
          if (aa >= (1 << 8)) {
    556
            aa >>= 8;
    557
            xn <<= 4;
    558
          }
    559
          if (aa >= (1 << 4)) {
    560
            aa >>= 4;
    561
            xn <<= 2;
    562
          }
    563
          if (aa >= (1 << 2)) {
    564
            xn <<= 1;
    565
          }
    566
    567
          // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
    568
          //
    569
          // We can refine our estimation by noticing that the middle of that interval minimizes the error.
    570
          // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
    571
          // This is going to be our x_0 (and ε_0)
    572
          xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
    573
    574
          // From here, Newton's method give us:
    575
          // x_{n+1} = (x_n + a / x_n) / 2
    576
          //
    577
          // One should note that:
    578
          // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
    579
          //              = ((x_n² + a) / (2 * x_n))² - a
    580
          //              = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
    581
          //              = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
    582
          //              = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
    583
          //              = (x_n² - a)² / (2 * x_n)²
    584
          //              = ((x_n² - a) / (2 * x_n))²
    585
          //              ≥ 0
    586
          // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
    587
          //
    588
          // This gives us the proof of quadratic convergence of the sequence:
    589
          // ε_{n+1} = | x_{n+1} - sqrt(a) |
    590
          //         = | (x_n + a / x_n) / 2 - sqrt(a) |
    591
          //         = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
    592
          //         = | (x_n - sqrt(a))² / (2 * x_n) |
    593
          //         = | ε_n² / (2 * x_n) |
    594
          //         = ε_n² / | (2 * x_n) |
    595
          //
    596
          // For the first iteration, we have a special case where x_0 is known:
    597
          // ε_1 = ε_0² / | (2 * x_0) |
    598
          //     ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
    599
          //     ≤ 2**(2*e-4) / (3 * 2**(e-1))
    600
          //     ≤ 2**(e-3) / 3
    601
          //     ≤ 2**(e-3-log2(3))
    602
          //     ≤ 2**(e-4.5)
    603
          //
    604
          // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
    605
          // ε_{n+1} = ε_n² / | (2 * x_n) |
    606
          //         ≤ (2**(e-k))² / (2 * 2**(e-1))
    607
          //         ≤ 2**(2*e-2*k) / 2**e
    608
          //         ≤ 2**(e-2*k)
    609
          xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5)  -- special case, see above
    610
          xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9)    -- general case with k = 4.5
    611
          xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18)   -- general case with k = 9
    612
          xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36)   -- general case with k = 18
    613
          xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72)   -- general case with k = 36
    614
          xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144)  -- general case with k = 72
    615
    616
          // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
    617
          // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
    618
          // sqrt(a) or sqrt(a) + 1.
    619
          return xn - SafeCast.toUint(xn > a / xn);
    620
        }
    621
      }
    622
    623
      /**
    624
       * @dev Calculates sqrt(a), following the selected rounding direction.
    625
       */
    626
      function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
    627
        unchecked {
    628
          uint256 result = sqrt(a);
    629
          return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
    630
        }
    631
      }
    632
    633
      /**
    634
       * @dev Return the log in base 2 of a positive value rounded towards zero.
    635
       * Returns 0 if given 0.
    636
       */
    637
      function log2(uint256 x) internal pure returns (uint256 r) {
    638
        // If value has upper 128 bits set, log2 result is at least 128
    639
        r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
    640
        // If upper 64 bits of 128-bit half set, add 64 to result
    641
        r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
    642
        // If upper 32 bits of 64-bit half set, add 32 to result
    643
        r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
    644
        // If upper 16 bits of 32-bit half set, add 16 to result
    645
        r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
    646
        // If upper 8 bits of 16-bit half set, add 8 to result
    647
        r |= SafeCast.toUint((x >> r) > 0xff) << 3;
    648
        // If upper 4 bits of 8-bit half set, add 4 to result
    649
        r |= SafeCast.toUint((x >> r) > 0xf) << 2;
    650
    651
        // Shifts value right by the current result and use it as an index into this lookup table:
    652
        //
    653
        // | x (4 bits) |  index  | table[index] = MSB position |
    654
        // |------------|---------|-----------------------------|
    655
        // |    0000    |    0    |        table[0] = 0         |
    656
        // |    0001    |    1    |        table[1] = 0         |
    657
        // |    0010    |    2    |        table[2] = 1         |
    658
        // |    0011    |    3    |        table[3] = 1         |
    659
        // |    0100    |    4    |        table[4] = 2         |
    660
        // |    0101    |    5    |        table[5] = 2         |
    661
        // |    0110    |    6    |        table[6] = 2         |
    662
        // |    0111    |    7    |        table[7] = 2         |
    663
        // |    1000    |    8    |        table[8] = 3         |
    664
        // |    1001    |    9    |        table[9] = 3         |
    665
        // |    1010    |   10    |        table[10] = 3        |
    666
        // |    1011    |   11    |        table[11] = 3        |
    667
        // |    1100    |   12    |        table[12] = 3        |
    668
        // |    1101    |   13    |        table[13] = 3        |
    669
        // |    1110    |   14    |        table[14] = 3        |
    670
        // |    1111    |   15    |        table[15] = 3        |
    671
        //
    672
        // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
    673
        assembly ('memory-safe') {
    674
          r := or(
    675
            r,
    676
            byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000)
    677
          )
    678
        }
    679
      }
    680
    681
      /**
    682
       * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
    683
       * Returns 0 if given 0.
    684
       */
    685
      function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
    686
        unchecked {
    687
          uint256 result = log2(value);
    688
          return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
    689
        }
    690
      }
    691
    692
      /**
    693
       * @dev Return the log in base 10 of a positive value rounded towards zero.
    694
       * Returns 0 if given 0.
    695
       */
    696
      function log10(uint256 value) internal pure returns (uint256) {
    697
        uint256 result = 0;
    698
        unchecked {
    699
          if (value >= 10 ** 64) {
    700
            value /= 10 ** 64;
    701
            result += 64;
    702
          }
    703
          if (value >= 10 ** 32) {
    704
            value /= 10 ** 32;
    705
            result += 32;
    706
          }
    707
          if (value >= 10 ** 16) {
    708
            value /= 10 ** 16;
    709
            result += 16;
    710
          }
    711
          if (value >= 10 ** 8) {
    712
            value /= 10 ** 8;
    713
            result += 8;
    714
          }
    715
          if (value >= 10 ** 4) {
    716
            value /= 10 ** 4;
    717
            result += 4;
    718
          }
    719
          if (value >= 10 ** 2) {
    720
            value /= 10 ** 2;
    721
            result += 2;
    722
          }
    723
          if (value >= 10 ** 1) {
    724
            result += 1;
    725
          }
    726
        }
    727
        return result;
    728
      }
    729
    730
      /**
    731
       * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
    732
       * Returns 0 if given 0.
    733
       */
    734
      function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
    735
        unchecked {
    736
          uint256 result = log10(value);
    737
          return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
    738
        }
    739
      }
    740
    741
      /**
    742
       * @dev Return the log in base 256 of a positive value rounded towards zero.
    743
       * Returns 0 if given 0.
    744
       *
    745
       * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
    746
       */
    747
      function log256(uint256 x) internal pure returns (uint256 r) {
    748
        // If value has upper 128 bits set, log2 result is at least 128
    749
        r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
    750
        // If upper 64 bits of 128-bit half set, add 64 to result
    751
        r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
    752
        // If upper 32 bits of 64-bit half set, add 32 to result
    753
        r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
    754
        // If upper 16 bits of 32-bit half set, add 16 to result
    755
        r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
    756
        // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
    757
        return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
    758
      }
    759
    760
      /**
    761
       * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
    762
       * Returns 0 if given 0.
    763
       */
    764
      function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
    765
        unchecked {
    766
          uint256 result = log256(value);
    767
          return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
    768
        }
    769
      }
    770
    771
      /**
    772
       * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
    773
       */
    774 0
      function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
    775 0
        return uint8(rounding) % 2 == 1;
    776
      }
    777
    }
    0% src/dependencies/openzeppelin/Multicall.sol
    Lines covered: 0 / 7 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/Multicall.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Address} from './Address.sol';
    7
    import {Context} from './Context.sol';
    8
    9
    /**
    10
     * @dev Provides a function to batch together multiple calls in a single external call.
    11
     *
    12
     * Consider any assumption about calldata validation performed by the sender may be violated if it's not especially
    13
     * careful about sending transactions invoking {multicall}. For example, a relay address that filters function
    14
     * selectors won't filter calls nested within a {multicall} operation.
    15
     *
    16
     * NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {Context-_msgSender}).
    17
     * If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data`
    18
     * to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of
    19
     * {Context-_msgSender} are not propagated to subcalls.
    20
     */
    21
    abstract contract Multicall is Context {
    22
      /**
    23
       * @dev Receives and executes a batch of function calls on this contract.
    24
       * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
    25
       */
    26 0
      function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
    27 0
        bytes memory context = msg.sender == _msgSender()
    28 0
          ? new bytes(0)
    29
          : msg.data[msg.data.length - _contextSuffixLength():];
    30
    31 0
        results = new bytes[](data.length);
    32 0
        for (uint256 i = 0; i < data.length; i++) {
    33 0
          results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context));
    34
        }
    35 0
        return results;
    36
      }
    37
    }
    28% src/dependencies/openzeppelin/Ownable.sol
    Lines covered: 6 / 21 (28%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Context} from './Context.sol';
    7
    8
    /**
    9
     * @dev Contract module which provides a basic access control mechanism, where
    10
     * there is an account (an owner) that can be granted exclusive access to
    11
     * specific functions.
    12
     *
    13
     * The initial owner is set to the address provided by the deployer. This can
    14
     * later be changed with {transferOwnership}.
    15
     *
    16
     * This module is used through inheritance. It will make available the modifier
    17
     * `onlyOwner`, which can be applied to your functions to restrict their use to
    18
     * the owner.
    19
     */
    20
    abstract contract Ownable is Context {
    21
      address private _owner;
    22
    23
      /**
    24
       * @dev The caller account is not authorized to perform an operation.
    25
       */
    26
      error OwnableUnauthorizedAccount(address account);
    27
    28
      /**
    29
       * @dev The owner is not a valid owner account. (eg. `address(0)`)
    30
       */
    31
      error OwnableInvalidOwner(address owner);
    32
    33
      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    34
    35
      /**
    36
       * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
    37
       */
    38 0
      constructor(address initialOwner) {
    39
        if (initialOwner == address(0)) {
    40 0
          revert OwnableInvalidOwner(address(0));
    41
        }
    42
        _transferOwnership(initialOwner);
    43
      }
    44
    45
      /**
    46
       * @dev Throws if called by any account other than the owner.
    47
       */
    48
      modifier onlyOwner() {
    49 0
        _checkOwner();
    50 0
        _;
    51
      }
    52
    53
      /**
    54
       * @dev Returns the address of the current owner.
    55
       */
    56 0
      function owner() public view virtual returns (address) {
    57 0
        return _owner;
    58
      }
    59
    60
      /**
    61
       * @dev Throws if the sender is not the owner.
    62
       */
    63 0
      function _checkOwner() internal view virtual {
    64 0
        if (owner() != _msgSender()) {
    65 0
          revert OwnableUnauthorizedAccount(_msgSender());
    66
        }
    67
      }
    68
    69
      /**
    70
       * @dev Leaves the contract without owner. It will not be possible to call
    71
       * `onlyOwner` functions. Can only be called by the current owner.
    72
       *
    73
       * NOTE: Renouncing ownership will leave the contract without an owner,
    74
       * thereby disabling any functionality that is only available to the owner.
    75
       */
    76 0
      function renounceOwnership() public virtual onlyOwner {
    77 0
        _transferOwnership(address(0));
    78
      }
    79
    80
      /**
    81
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
    82
       * Can only be called by the current owner.
    83
       */
    84 0
      function transferOwnership(address newOwner) public virtual onlyOwner {
    85 0
        if (newOwner == address(0)) {
    86 0
          revert OwnableInvalidOwner(address(0));
    87
        }
    88 0
        _transferOwnership(newOwner);
    89
      }
    90
    91
      /**
    92
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
    93
       * Internal function without access restriction.
    94
       */
    95
      function _transferOwnership(address newOwner) internal virtual {
    96 11×
        address oldOwner = _owner;
    97
        _owner = newOwner;
    98
        emit OwnershipTransferred(oldOwner, newOwner);
    99
      }
    100
    }
    25% src/dependencies/openzeppelin/Ownable2Step.sol
    Lines covered: 3 / 12 (25%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Ownable} from './Ownable.sol';
    7
    8
    /**
    9
     * @dev Contract module which provides access control mechanism, where
    10
     * there is an account (an owner) that can be granted exclusive access to
    11
     * specific functions.
    12
     *
    13
     * This extension of the {Ownable} contract includes a two-step mechanism to transfer
    14
     * ownership, where the new owner must call {acceptOwnership} in order to replace the
    15
     * old one. This can help prevent common mistakes, such as transfers of ownership to
    16
     * incorrect accounts, or to contracts that are unable to interact with the
    17
     * permission system.
    18
     *
    19
     * The initial owner is specified at deployment time in the constructor for `Ownable`. This
    20
     * can later be changed with {transferOwnership} and {acceptOwnership}.
    21
     *
    22
     * This module is used through inheritance. It will make available all functions
    23
     * from parent (Ownable).
    24
     */
    25
    abstract contract Ownable2Step is Ownable {
    26
      address private _pendingOwner;
    27
    28
      event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
    29
    30
      /**
    31
       * @dev Returns the address of the pending owner.
    32
       */
    33 0
      function pendingOwner() public view virtual returns (address) {
    34 0
        return _pendingOwner;
    35
      }
    36
    37
      /**
    38
       * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
    39
       * Can only be called by the current owner.
    40
       *
    41
       * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
    42
       */
    43 0
      function transferOwnership(address newOwner) public virtual override onlyOwner {
    44 0
        _pendingOwner = newOwner;
    45 0
        emit OwnershipTransferStarted(owner(), newOwner);
    46
      }
    47
    48
      /**
    49
       * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
    50
       * Internal function without access restriction.
    51
       */
    52
      function _transferOwnership(address newOwner) internal virtual override {
    53
        delete _pendingOwner;
    54
        super._transferOwnership(newOwner);
    55
      }
    56
    57
      /**
    58
       * @dev The new owner accepts the ownership transfer.
    59
       */
    60 0
      function acceptOwnership() public virtual {
    61
        address sender = _msgSender();
    62 0
        if (pendingOwner() != sender) {
    63 0
          revert OwnableUnauthorizedAccount(sender);
    64
        }
    65 0
        _transferOwnership(sender);
    66
      }
    67
    }
    0% src/dependencies/openzeppelin/Panic.sol
    Lines covered: 0 / 6 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Helper library for emitting standardized panic codes.
    8
     *
    9
     * ```solidity
    10
     * contract Example {
    11
     *      using Panic for uint256;
    12
     *
    13
     *      // Use any of the declared internal constants
    14
     *      function foo() { Panic.GENERIC.panic(); }
    15
     *
    16
     *      // Alternatively
    17
     *      function foo() { Panic.panic(Panic.GENERIC); }
    18
     * }
    19
     * ```
    20
     *
    21
     * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
    22
     *
    23
     * _Available since v5.1._
    24
     */
    25
    // slither-disable-next-line unused-state
    26 0
    library Panic {
    27
      /// @dev generic / unspecified error
    28
      uint256 internal constant GENERIC = 0x00;
    29
      /// @dev used by the assert() builtin
    30
      uint256 internal constant ASSERT = 0x01;
    31
      /// @dev arithmetic underflow or overflow
    32 0
      uint256 internal constant UNDER_OVERFLOW = 0x11;
    33
      /// @dev division or modulo by zero
    34
      uint256 internal constant DIVISION_BY_ZERO = 0x12;
    35
      /// @dev enum conversion error
    36
      uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
    37
      /// @dev invalid encoding in storage
    38
      uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
    39
      /// @dev empty array pop
    40
      uint256 internal constant EMPTY_ARRAY_POP = 0x31;
    41
      /// @dev array out of bounds access
    42
      uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
    43
      /// @dev resource error (too large allocation or too large array)
    44
      uint256 internal constant RESOURCE_ERROR = 0x41;
    45
      /// @dev calling invalid internal function
    46
      uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
    47
    48
      /// @dev Reverts with a panic code. Recommended to use with
    49
      /// the internal constants with predefined codes.
    50 0
      function panic(uint256 code) internal pure {
    51
        assembly ('memory-safe') {
    52 0
          mstore(0x00, 0x4e487b71)
    53 0
          mstore(0x20, code)
    54 0
          revert(0x1c, 0x24)
    55
        }
    56
      }
    57
    }
    0% src/dependencies/openzeppelin/Proxy.sol
    Lines covered: 0 / 11 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
    8
     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
    9
     * be specified by overriding the virtual {_implementation} function.
    10
     *
    11
     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
    12
     * different contract through the {_delegate} function.
    13
     *
    14
     * The success and return data of the delegated call will be returned back to the caller of the proxy.
    15
     */
    16
    abstract contract Proxy {
    17
        /**
    18
         * @dev Delegates the current call to `implementation`.
    19
         *
    20
         * This function does not return to its internal call site, it will return directly to the external caller.
    21
         */
    22 0
        function _delegate(address implementation) internal virtual {
    23
            assembly {
    24
                // Copy msg.data. We take full control of memory in this inline assembly
    25
                // block because it will not return to Solidity code. We overwrite the
    26
                // Solidity scratch pad at memory position 0.
    27 0
                calldatacopy(0, 0, calldatasize())
    28
    29
                // Call the implementation.
    30
                // out and outsize are 0 because we don't know the size yet.
    31 0
                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
    32
    33
                // Copy the returned data.
    34 0
                returndatacopy(0, 0, returndatasize())
    35
    36 0
                switch result
    37
                // delegatecall returns 0 on error.
    38 0
                case 0 {
    39 0
                    revert(0, returndatasize())
    40
                }
    41
                default {
    42 0
                    return(0, returndatasize())
    43
                }
    44
            }
    45
        }
    46
    47
        /**
    48
         * @dev This is a virtual function that should be overridden so it returns the address to which the fallback
    49
         * function and {_fallback} should delegate.
    50
         */
    51
        function _implementation() internal view virtual returns (address);
    52
    53
        /**
    54
         * @dev Delegates the current call to the address returned by `_implementation()`.
    55
         *
    56
         * This function does not return to its internal call site, it will return directly to the external caller.
    57
         */
    58 0
        function _fallback() internal virtual {
    59 0
            _delegate(_implementation());
    60
        }
    61
    62
        /**
    63
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
    64
         * function in the contract matches the call data.
    65
         */
    66
        fallback() external payable virtual {
    67 0
            _fallback();
    68
        }
    69
    }
    0% src/dependencies/openzeppelin/ProxyAdmin.sol
    Lines covered: 0 / 5 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/ProxyAdmin.sol)
    3
    4
    pragma solidity ^0.8.22;
    5
    6
    import {ITransparentUpgradeableProxy} from './TransparentUpgradeableProxy.sol';
    7
    import {Ownable} from './Ownable.sol';
    8
    9
    /**
    10
     * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
    11
     * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
    12
     */
    13 0
    contract ProxyAdmin is Ownable {
    14
      /**
    15
       * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address,address)`
    16
       * and `upgradeAndCall(address,address,bytes)` are present, and `upgrade` must be used if no function should be called,
    17
       * while `upgradeAndCall` will invoke the `receive` function if the third argument is the empty byte string.
    18
       * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,address,bytes)` is present, and the third argument must
    19
       * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
    20
       * during an upgrade.
    21
       */
    22 0
      string public constant UPGRADE_INTERFACE_VERSION = '5.0.0';
    23
    24
      /**
    25
       * @dev Sets the initial owner who can perform upgrades.
    26
       */
    27 0
      constructor(address initialOwner) Ownable(initialOwner) {}
    28
    29
      /**
    30
       * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
    31
       * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
    32
       *
    33
       * Requirements:
    34
       *
    35
       * - This contract must be the admin of `proxy`.
    36
       * - If `data` is empty, `msg.value` must be zero.
    37
       */
    38 0
      function upgradeAndCall(
    39
        ITransparentUpgradeableProxy proxy,
    40
        address implementation,
    41
        bytes memory data
    42
      ) public payable virtual onlyOwner {
    43 0
        proxy.upgradeToAndCall{value: msg.value}(implementation, data);
    44
      }
    45
    }
    0% src/dependencies/openzeppelin/ReentrancyGuardTransient.sol
    Lines covered: 0 / 9 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/ReentrancyGuardTransient.sol)
    3
    4
    pragma solidity ^0.8.24;
    5
    6
    import {TransientSlot} from './TransientSlot.sol';
    7
    8
    /**
    9
     * @dev Variant of {ReentrancyGuard} that uses transient storage.
    10
     *
    11
     * NOTE: This variant only works on networks where EIP-1153 is available.
    12
     *
    13
     * _Available since v5.1._
    14
     */
    15
    abstract contract ReentrancyGuardTransient {
    16
      using TransientSlot for *;
    17
    18
      // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    19
      bytes32 private constant REENTRANCY_GUARD_STORAGE =
    20
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
    21
    22
      /**
    23
       * @dev Unauthorized reentrant call.
    24
       */
    25
      error ReentrancyGuardReentrantCall();
    26
    27
      /**
    28
       * @dev Prevents a contract from calling itself, directly or indirectly.
    29
       * Calling a `nonReentrant` function from another `nonReentrant`
    30
       * function is not supported. It is possible to prevent this from happening
    31
       * by making the `nonReentrant` function external, and making it call a
    32
       * `private` function that does the actual work.
    33
       */
    34
      modifier nonReentrant() {
    35 0
        _nonReentrantBefore();
    36 0
        _;
    37 0
        _nonReentrantAfter();
    38
      }
    39
    40 0
      function _nonReentrantBefore() private {
    41
        // On the first call to nonReentrant, REENTRANCY_GUARD_STORAGE.asBoolean().tload() will be false
    42 0
        if (_reentrancyGuardEntered()) {
    43 0
          revert ReentrancyGuardReentrantCall();
    44
        }
    45
    46
        // Any calls to nonReentrant after this point will fail
    47 0
        REENTRANCY_GUARD_STORAGE.asBoolean().tstore(true);
    48
      }
    49
    50 0
      function _nonReentrantAfter() private {
    51 0
        REENTRANCY_GUARD_STORAGE.asBoolean().tstore(false);
    52
      }
    53
    54
      /**
    55
       * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
    56
       * `nonReentrant` function in the call stack.
    57
       */
    58
      function _reentrancyGuardEntered() internal view returns (bool) {
    59
        return REENTRANCY_GUARD_STORAGE.asBoolean().tload();
    60
      }
    61
    }
    32% src/dependencies/openzeppelin/SafeCast.sol
    Lines covered: 21 / 64 (32%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/SafeCast.js.
    4
    5
    pragma solidity ^0.8.20;
    6
    7
    /**
    8
     * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
    9
     * checks.
    10
     *
    11
     * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
    12
     * easily result in undesired exploitation or bugs, since developers usually
    13
     * assume that overflows raise errors. `SafeCast` restores this intuition by
    14
     * reverting the transaction when such an operation overflows.
    15
     *
    16
     * Using this library instead of the unchecked operations eliminates an entire
    17
     * class of bugs, so it's recommended to use it always.
    18
     */
    19 0
    library SafeCast {
    20
      /**
    21
       * @dev Value doesn't fit in an uint of `bits` size.
    22
       */
    23
      error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
    24
    25
      /**
    26
       * @dev An int value doesn't fit in an uint of `bits` size.
    27
       */
    28
      error SafeCastOverflowedIntToUint(int256 value);
    29
    30
      /**
    31
       * @dev Value doesn't fit in an int of `bits` size.
    32
       */
    33
      error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
    34
    35
      /**
    36
       * @dev An uint value doesn't fit in an int of `bits` size.
    37
       */
    38
      error SafeCastOverflowedUintToInt(uint256 value);
    39
    40
      /**
    41
       * @dev Returns the downcasted uint248 from uint256, reverting on
    42
       * overflow (when the input is greater than largest uint248).
    43
       *
    44
       * Counterpart to Solidity's `uint248` operator.
    45
       *
    46
       * Requirements:
    47
       *
    48
       * - input must fit into 248 bits
    49
       */
    50
      function toUint248(uint256 value) internal pure returns (uint248) {
    51
        if (value > type(uint248).max) {
    52
          revert SafeCastOverflowedUintDowncast(248, value);
    53
        }
    54
        return uint248(value);
    55
      }
    56
    57
      /**
    58
       * @dev Returns the downcasted uint240 from uint256, reverting on
    59
       * overflow (when the input is greater than largest uint240).
    60
       *
    61
       * Counterpart to Solidity's `uint240` operator.
    62
       *
    63
       * Requirements:
    64
       *
    65
       * - input must fit into 240 bits
    66
       */
    67
      function toUint240(uint256 value) internal pure returns (uint240) {
    68
        if (value > type(uint240).max) {
    69
          revert SafeCastOverflowedUintDowncast(240, value);
    70
        }
    71
        return uint240(value);
    72
      }
    73
    74
      /**
    75
       * @dev Returns the downcasted uint232 from uint256, reverting on
    76
       * overflow (when the input is greater than largest uint232).
    77
       *
    78
       * Counterpart to Solidity's `uint232` operator.
    79
       *
    80
       * Requirements:
    81
       *
    82
       * - input must fit into 232 bits
    83
       */
    84
      function toUint232(uint256 value) internal pure returns (uint232) {
    85
        if (value > type(uint232).max) {
    86
          revert SafeCastOverflowedUintDowncast(232, value);
    87
        }
    88
        return uint232(value);
    89
      }
    90
    91
      /**
    92
       * @dev Returns the downcasted uint224 from uint256, reverting on
    93
       * overflow (when the input is greater than largest uint224).
    94
       *
    95
       * Counterpart to Solidity's `uint224` operator.
    96
       *
    97
       * Requirements:
    98
       *
    99
       * - input must fit into 224 bits
    100
       */
    101
      function toUint224(uint256 value) internal pure returns (uint224) {
    102
        if (value > type(uint224).max) {
    103
          revert SafeCastOverflowedUintDowncast(224, value);
    104
        }
    105
        return uint224(value);
    106
      }
    107
    108
      /**
    109
       * @dev Returns the downcasted uint216 from uint256, reverting on
    110
       * overflow (when the input is greater than largest uint216).
    111
       *
    112
       * Counterpart to Solidity's `uint216` operator.
    113
       *
    114
       * Requirements:
    115
       *
    116
       * - input must fit into 216 bits
    117
       */
    118
      function toUint216(uint256 value) internal pure returns (uint216) {
    119
        if (value > type(uint216).max) {
    120
          revert SafeCastOverflowedUintDowncast(216, value);
    121
        }
    122
        return uint216(value);
    123
      }
    124
    125
      /**
    126
       * @dev Returns the downcasted uint208 from uint256, reverting on
    127
       * overflow (when the input is greater than largest uint208).
    128
       *
    129
       * Counterpart to Solidity's `uint208` operator.
    130
       *
    131
       * Requirements:
    132
       *
    133
       * - input must fit into 208 bits
    134
       */
    135
      function toUint208(uint256 value) internal pure returns (uint208) {
    136
        if (value > type(uint208).max) {
    137
          revert SafeCastOverflowedUintDowncast(208, value);
    138
        }
    139
        return uint208(value);
    140
      }
    141
    142
      /**
    143
       * @dev Returns the downcasted uint200 from uint256, reverting on
    144
       * overflow (when the input is greater than largest uint200).
    145
       *
    146
       * Counterpart to Solidity's `uint200` operator.
    147
       *
    148
       * Requirements:
    149
       *
    150
       * - input must fit into 200 bits
    151
       */
    152
      function toUint200(uint256 value) internal pure returns (uint200) {
    153
        if (value > type(uint200).max) {
    154 0
          revert SafeCastOverflowedUintDowncast(200, value);
    155
        }
    156
        return uint200(value);
    157
      }
    158
    159
      /**
    160
       * @dev Returns the downcasted uint192 from uint256, reverting on
    161
       * overflow (when the input is greater than largest uint192).
    162
       *
    163
       * Counterpart to Solidity's `uint192` operator.
    164
       *
    165
       * Requirements:
    166
       *
    167
       * - input must fit into 192 bits
    168
       */
    169 0
      function toUint192(uint256 value) internal pure returns (uint192) {
    170 0
        if (value > type(uint192).max) {
    171 0
          revert SafeCastOverflowedUintDowncast(192, value);
    172
        }
    173 0
        return uint192(value);
    174
      }
    175
    176
      /**
    177
       * @dev Returns the downcasted uint184 from uint256, reverting on
    178
       * overflow (when the input is greater than largest uint184).
    179
       *
    180
       * Counterpart to Solidity's `uint184` operator.
    181
       *
    182
       * Requirements:
    183
       *
    184
       * - input must fit into 184 bits
    185
       */
    186
      function toUint184(uint256 value) internal pure returns (uint184) {
    187
        if (value > type(uint184).max) {
    188
          revert SafeCastOverflowedUintDowncast(184, value);
    189
        }
    190
        return uint184(value);
    191
      }
    192
    193
      /**
    194
       * @dev Returns the downcasted uint176 from uint256, reverting on
    195
       * overflow (when the input is greater than largest uint176).
    196
       *
    197
       * Counterpart to Solidity's `uint176` operator.
    198
       *
    199
       * Requirements:
    200
       *
    201
       * - input must fit into 176 bits
    202
       */
    203
      function toUint176(uint256 value) internal pure returns (uint176) {
    204
        if (value > type(uint176).max) {
    205
          revert SafeCastOverflowedUintDowncast(176, value);
    206
        }
    207
        return uint176(value);
    208
      }
    209
    210
      /**
    211
       * @dev Returns the downcasted uint168 from uint256, reverting on
    212
       * overflow (when the input is greater than largest uint168).
    213
       *
    214
       * Counterpart to Solidity's `uint168` operator.
    215
       *
    216
       * Requirements:
    217
       *
    218
       * - input must fit into 168 bits
    219
       */
    220
      function toUint168(uint256 value) internal pure returns (uint168) {
    221
        if (value > type(uint168).max) {
    222
          revert SafeCastOverflowedUintDowncast(168, value);
    223
        }
    224
        return uint168(value);
    225
      }
    226
    227
      /**
    228
       * @dev Returns the downcasted uint160 from uint256, reverting on
    229
       * overflow (when the input is greater than largest uint160).
    230
       *
    231
       * Counterpart to Solidity's `uint160` operator.
    232
       *
    233
       * Requirements:
    234
       *
    235
       * - input must fit into 160 bits
    236
       */
    237
      function toUint160(uint256 value) internal pure returns (uint160) {
    238
        if (value > type(uint160).max) {
    239
          revert SafeCastOverflowedUintDowncast(160, value);
    240
        }
    241
        return uint160(value);
    242
      }
    243
    244
      /**
    245
       * @dev Returns the downcasted uint152 from uint256, reverting on
    246
       * overflow (when the input is greater than largest uint152).
    247
       *
    248
       * Counterpart to Solidity's `uint152` operator.
    249
       *
    250
       * Requirements:
    251
       *
    252
       * - input must fit into 152 bits
    253
       */
    254
      function toUint152(uint256 value) internal pure returns (uint152) {
    255
        if (value > type(uint152).max) {
    256
          revert SafeCastOverflowedUintDowncast(152, value);
    257
        }
    258
        return uint152(value);
    259
      }
    260
    261
      /**
    262
       * @dev Returns the downcasted uint144 from uint256, reverting on
    263
       * overflow (when the input is greater than largest uint144).
    264
       *
    265
       * Counterpart to Solidity's `uint144` operator.
    266
       *
    267
       * Requirements:
    268
       *
    269
       * - input must fit into 144 bits
    270
       */
    271
      function toUint144(uint256 value) internal pure returns (uint144) {
    272
        if (value > type(uint144).max) {
    273
          revert SafeCastOverflowedUintDowncast(144, value);
    274
        }
    275
        return uint144(value);
    276
      }
    277
    278
      /**
    279
       * @dev Returns the downcasted uint136 from uint256, reverting on
    280
       * overflow (when the input is greater than largest uint136).
    281
       *
    282
       * Counterpart to Solidity's `uint136` operator.
    283
       *
    284
       * Requirements:
    285
       *
    286
       * - input must fit into 136 bits
    287
       */
    288
      function toUint136(uint256 value) internal pure returns (uint136) {
    289
        if (value > type(uint136).max) {
    290
          revert SafeCastOverflowedUintDowncast(136, value);
    291
        }
    292
        return uint136(value);
    293
      }
    294
    295
      /**
    296
       * @dev Returns the downcasted uint128 from uint256, reverting on
    297
       * overflow (when the input is greater than largest uint128).
    298
       *
    299
       * Counterpart to Solidity's `uint128` operator.
    300
       *
    301
       * Requirements:
    302
       *
    303
       * - input must fit into 128 bits
    304
       */
    305 0
      function toUint128(uint256 value) internal pure returns (uint128) {
    306 0
        if (value > type(uint128).max) {
    307 0
          revert SafeCastOverflowedUintDowncast(128, value);
    308
        }
    309 0
        return uint128(value);
    310
      }
    311
    312
      /**
    313
       * @dev Returns the downcasted uint120 from uint256, reverting on
    314
       * overflow (when the input is greater than largest uint120).
    315
       *
    316
       * Counterpart to Solidity's `uint120` operator.
    317
       *
    318
       * Requirements:
    319
       *
    320
       * - input must fit into 120 bits
    321
       */
    322 15×
      function toUint120(uint256 value) internal pure returns (uint120) {
    323 30×
        if (value > type(uint120).max) {
    324 0
          revert SafeCastOverflowedUintDowncast(120, value);
    325
        }
    326
        return uint120(value);
    327
      }
    328
    329
      /**
    330
       * @dev Returns the downcasted uint112 from uint256, reverting on
    331
       * overflow (when the input is greater than largest uint112).
    332
       *
    333
       * Counterpart to Solidity's `uint112` operator.
    334
       *
    335
       * Requirements:
    336
       *
    337
       * - input must fit into 112 bits
    338
       */
    339
      function toUint112(uint256 value) internal pure returns (uint112) {
    340
        if (value > type(uint112).max) {
    341
          revert SafeCastOverflowedUintDowncast(112, value);
    342
        }
    343
        return uint112(value);
    344
      }
    345
    346
      /**
    347
       * @dev Returns the downcasted uint104 from uint256, reverting on
    348
       * overflow (when the input is greater than largest uint104).
    349
       *
    350
       * Counterpart to Solidity's `uint104` operator.
    351
       *
    352
       * Requirements:
    353
       *
    354
       * - input must fit into 104 bits
    355
       */
    356
      function toUint104(uint256 value) internal pure returns (uint104) {
    357
        if (value > type(uint104).max) {
    358
          revert SafeCastOverflowedUintDowncast(104, value);
    359
        }
    360
        return uint104(value);
    361
      }
    362
    363
      /**
    364
       * @dev Returns the downcasted uint96 from uint256, reverting on
    365
       * overflow (when the input is greater than largest uint96).
    366
       *
    367
       * Counterpart to Solidity's `uint96` operator.
    368
       *
    369
       * Requirements:
    370
       *
    371
       * - input must fit into 96 bits
    372
       */
    373
      function toUint96(uint256 value) internal pure returns (uint96) {
    374 15×
        if (value > type(uint96).max) {
    375 0
          revert SafeCastOverflowedUintDowncast(96, value);
    376
        }
    377 0
        return uint96(value);
    378
      }
    379
    380
      /**
    381
       * @dev Returns the downcasted uint88 from uint256, reverting on
    382
       * overflow (when the input is greater than largest uint88).
    383
       *
    384
       * Counterpart to Solidity's `uint88` operator.
    385
       *
    386
       * Requirements:
    387
       *
    388
       * - input must fit into 88 bits
    389
       */
    390
      function toUint88(uint256 value) internal pure returns (uint88) {
    391
        if (value > type(uint88).max) {
    392
          revert SafeCastOverflowedUintDowncast(88, value);
    393
        }
    394
        return uint88(value);
    395
      }
    396
    397
      /**
    398
       * @dev Returns the downcasted uint80 from uint256, reverting on
    399
       * overflow (when the input is greater than largest uint80).
    400
       *
    401
       * Counterpart to Solidity's `uint80` operator.
    402
       *
    403
       * Requirements:
    404
       *
    405
       * - input must fit into 80 bits
    406
       */
    407 0
      function toUint80(uint256 value) internal pure returns (uint80) {
    408 0
        if (value > type(uint80).max) {
    409 0
          revert SafeCastOverflowedUintDowncast(80, value);
    410
        }
    411
        return uint80(value);
    412
      }
    413
    414
      /**
    415
       * @dev Returns the downcasted uint72 from uint256, reverting on
    416
       * overflow (when the input is greater than largest uint72).
    417
       *
    418
       * Counterpart to Solidity's `uint72` operator.
    419
       *
    420
       * Requirements:
    421
       *
    422
       * - input must fit into 72 bits
    423
       */
    424
      function toUint72(uint256 value) internal pure returns (uint72) {
    425
        if (value > type(uint72).max) {
    426
          revert SafeCastOverflowedUintDowncast(72, value);
    427
        }
    428
        return uint72(value);
    429
      }
    430
    431
      /**
    432
       * @dev Returns the downcasted uint64 from uint256, reverting on
    433
       * overflow (when the input is greater than largest uint64).
    434
       *
    435
       * Counterpart to Solidity's `uint64` operator.
    436
       *
    437
       * Requirements:
    438
       *
    439
       * - input must fit into 64 bits
    440
       */
    441 0
      function toUint64(uint256 value) internal pure returns (uint64) {
    442 0
        if (value > type(uint64).max) {
    443 0
          revert SafeCastOverflowedUintDowncast(64, value);
    444
        }
    445 0
        return uint64(value);
    446
      }
    447
    448
      /**
    449
       * @dev Returns the downcasted uint56 from uint256, reverting on
    450
       * overflow (when the input is greater than largest uint56).
    451
       *
    452
       * Counterpart to Solidity's `uint56` operator.
    453
       *
    454
       * Requirements:
    455
       *
    456
       * - input must fit into 56 bits
    457
       */
    458
      function toUint56(uint256 value) internal pure returns (uint56) {
    459
        if (value > type(uint56).max) {
    460
          revert SafeCastOverflowedUintDowncast(56, value);
    461
        }
    462
        return uint56(value);
    463
      }
    464
    465
      /**
    466
       * @dev Returns the downcasted uint48 from uint256, reverting on
    467
       * overflow (when the input is greater than largest uint48).
    468
       *
    469
       * Counterpart to Solidity's `uint48` operator.
    470
       *
    471
       * Requirements:
    472
       *
    473
       * - input must fit into 48 bits
    474
       */
    475
      function toUint48(uint256 value) internal pure returns (uint48) {
    476 21×
        if (value > type(uint48).max) {
    477 0
          revert SafeCastOverflowedUintDowncast(48, value);
    478
        }
    479
        return uint48(value);
    480
      }
    481
    482
      /**
    483
       * @dev Returns the downcasted uint40 from uint256, reverting on
    484
       * overflow (when the input is greater than largest uint40).
    485
       *
    486
       * Counterpart to Solidity's `uint40` operator.
    487
       *
    488
       * Requirements:
    489
       *
    490
       * - input must fit into 40 bits
    491
       */
    492
      function toUint40(uint256 value) internal pure returns (uint40) {
    493 18×
        if (value > type(uint40).max) {
    494 0
          revert SafeCastOverflowedUintDowncast(40, value);
    495
        }
    496 0
        return uint40(value);
    497
      }
    498
    499
      /**
    500
       * @dev Returns the downcasted uint32 from uint256, reverting on
    501
       * overflow (when the input is greater than largest uint32).
    502
       *
    503
       * Counterpart to Solidity's `uint32` operator.
    504
       *
    505
       * Requirements:
    506
       *
    507
       * - input must fit into 32 bits
    508
       */
    509 0
      function toUint32(uint256 value) internal pure returns (uint32) {
    510 0
        if (value > type(uint32).max) {
    511 0
          revert SafeCastOverflowedUintDowncast(32, value);
    512
        }
    513 0
        return uint32(value);
    514
      }
    515
    516
      /**
    517
       * @dev Returns the downcasted uint24 from uint256, reverting on
    518
       * overflow (when the input is greater than largest uint24).
    519
       *
    520
       * Counterpart to Solidity's `uint24` operator.
    521
       *
    522
       * Requirements:
    523
       *
    524
       * - input must fit into 24 bits
    525
       */
    526
      function toUint24(uint256 value) internal pure returns (uint24) {
    527
        if (value > type(uint24).max) {
    528 0
          revert SafeCastOverflowedUintDowncast(24, value);
    529
        }
    530 0
        return uint24(value);
    531
      }
    532
    533
      /**
    534
       * @dev Returns the downcasted uint16 from uint256, reverting on
    535
       * overflow (when the input is greater than largest uint16).
    536
       *
    537
       * Counterpart to Solidity's `uint16` operator.
    538
       *
    539
       * Requirements:
    540
       *
    541
       * - input must fit into 16 bits
    542
       */
    543 0
      function toUint16(uint256 value) internal pure returns (uint16) {
    544 0
        if (value > type(uint16).max) {
    545 0
          revert SafeCastOverflowedUintDowncast(16, value);
    546
        }
    547 0
        return uint16(value);
    548
      }
    549
    550
      /**
    551
       * @dev Returns the downcasted uint8 from uint256, reverting on
    552
       * overflow (when the input is greater than largest uint8).
    553
       *
    554
       * Counterpart to Solidity's `uint8` operator.
    555
       *
    556
       * Requirements:
    557
       *
    558
       * - input must fit into 8 bits
    559
       */
    560 0
      function toUint8(uint256 value) internal pure returns (uint8) {
    561 0
        if (value > type(uint8).max) {
    562 0
          revert SafeCastOverflowedUintDowncast(8, value);
    563
        }
    564 0
        return uint8(value);
    565
      }
    566
    567
      /**
    568
       * @dev Converts a signed int256 into an unsigned uint256.
    569
       *
    570
       * Requirements:
    571
       *
    572
       * - input must be greater than or equal to 0.
    573
       */
    574 10×
      function toUint256(int256 value) internal pure returns (uint256) {
    575 30×
        if (value < 0) {
    576 0
          revert SafeCastOverflowedIntToUint(value);
    577
        }
    578
        return uint256(value);
    579
      }
    580
    581
      /**
    582
       * @dev Returns the downcasted int248 from int256, reverting on
    583
       * overflow (when the input is less than smallest int248 or
    584
       * greater than largest int248).
    585
       *
    586
       * Counterpart to Solidity's `int248` operator.
    587
       *
    588
       * Requirements:
    589
       *
    590
       * - input must fit into 248 bits
    591
       */
    592
      function toInt248(int256 value) internal pure returns (int248 downcasted) {
    593
        downcasted = int248(value);
    594
        if (downcasted != value) {
    595
          revert SafeCastOverflowedIntDowncast(248, value);
    596
        }
    597
      }
    598
    599
      /**
    600
       * @dev Returns the downcasted int240 from int256, reverting on
    601
       * overflow (when the input is less than smallest int240 or
    602
       * greater than largest int240).
    603
       *
    604
       * Counterpart to Solidity's `int240` operator.
    605
       *
    606
       * Requirements:
    607
       *
    608
       * - input must fit into 240 bits
    609
       */
    610
      function toInt240(int256 value) internal pure returns (int240 downcasted) {
    611
        downcasted = int240(value);
    612
        if (downcasted != value) {
    613
          revert SafeCastOverflowedIntDowncast(240, value);
    614
        }
    615
      }
    616
    617
      /**
    618
       * @dev Returns the downcasted int232 from int256, reverting on
    619
       * overflow (when the input is less than smallest int232 or
    620
       * greater than largest int232).
    621
       *
    622
       * Counterpart to Solidity's `int232` operator.
    623
       *
    624
       * Requirements:
    625
       *
    626
       * - input must fit into 232 bits
    627
       */
    628
      function toInt232(int256 value) internal pure returns (int232 downcasted) {
    629
        downcasted = int232(value);
    630
        if (downcasted != value) {
    631
          revert SafeCastOverflowedIntDowncast(232, value);
    632
        }
    633
      }
    634
    635
      /**
    636
       * @dev Returns the downcasted int224 from int256, reverting on
    637
       * overflow (when the input is less than smallest int224 or
    638
       * greater than largest int224).
    639
       *
    640
       * Counterpart to Solidity's `int224` operator.
    641
       *
    642
       * Requirements:
    643
       *
    644
       * - input must fit into 224 bits
    645
       */
    646
      function toInt224(int256 value) internal pure returns (int224 downcasted) {
    647
        downcasted = int224(value);
    648
        if (downcasted != value) {
    649
          revert SafeCastOverflowedIntDowncast(224, value);
    650
        }
    651
      }
    652
    653
      /**
    654
       * @dev Returns the downcasted int216 from int256, reverting on
    655
       * overflow (when the input is less than smallest int216 or
    656
       * greater than largest int216).
    657
       *
    658
       * Counterpart to Solidity's `int216` operator.
    659
       *
    660
       * Requirements:
    661
       *
    662
       * - input must fit into 216 bits
    663
       */
    664
      function toInt216(int256 value) internal pure returns (int216 downcasted) {
    665
        downcasted = int216(value);
    666
        if (downcasted != value) {
    667
          revert SafeCastOverflowedIntDowncast(216, value);
    668
        }
    669
      }
    670
    671
      /**
    672
       * @dev Returns the downcasted int208 from int256, reverting on
    673
       * overflow (when the input is less than smallest int208 or
    674
       * greater than largest int208).
    675
       *
    676
       * Counterpart to Solidity's `int208` operator.
    677
       *
    678
       * Requirements:
    679
       *
    680
       * - input must fit into 208 bits
    681
       */
    682
      function toInt208(int256 value) internal pure returns (int208 downcasted) {
    683
        downcasted = int208(value);
    684
        if (downcasted != value) {
    685
          revert SafeCastOverflowedIntDowncast(208, value);
    686
        }
    687
      }
    688
    689
      /**
    690
       * @dev Returns the downcasted int200 from int256, reverting on
    691
       * overflow (when the input is less than smallest int200 or
    692
       * greater than largest int200).
    693
       *
    694
       * Counterpart to Solidity's `int200` operator.
    695
       *
    696
       * Requirements:
    697
       *
    698
       * - input must fit into 200 bits
    699
       */
    700
      function toInt200(int256 value) internal pure returns (int200 downcasted) {
    701
        downcasted = int200(value);
    702 32×
        if (downcasted != value) {
    703 0
          revert SafeCastOverflowedIntDowncast(200, value);
    704
        }
    705
      }
    706
    707
      /**
    708
       * @dev Returns the downcasted int192 from int256, reverting on
    709
       * overflow (when the input is less than smallest int192 or
    710
       * greater than largest int192).
    711
       *
    712
       * Counterpart to Solidity's `int192` operator.
    713
       *
    714
       * Requirements:
    715
       *
    716
       * - input must fit into 192 bits
    717
       */
    718
      function toInt192(int256 value) internal pure returns (int192 downcasted) {
    719
        downcasted = int192(value);
    720
        if (downcasted != value) {
    721
          revert SafeCastOverflowedIntDowncast(192, value);
    722
        }
    723
      }
    724
    725
      /**
    726
       * @dev Returns the downcasted int184 from int256, reverting on
    727
       * overflow (when the input is less than smallest int184 or
    728
       * greater than largest int184).
    729
       *
    730
       * Counterpart to Solidity's `int184` operator.
    731
       *
    732
       * Requirements:
    733
       *
    734
       * - input must fit into 184 bits
    735
       */
    736
      function toInt184(int256 value) internal pure returns (int184 downcasted) {
    737
        downcasted = int184(value);
    738
        if (downcasted != value) {
    739
          revert SafeCastOverflowedIntDowncast(184, value);
    740
        }
    741
      }
    742
    743
      /**
    744
       * @dev Returns the downcasted int176 from int256, reverting on
    745
       * overflow (when the input is less than smallest int176 or
    746
       * greater than largest int176).
    747
       *
    748
       * Counterpart to Solidity's `int176` operator.
    749
       *
    750
       * Requirements:
    751
       *
    752
       * - input must fit into 176 bits
    753
       */
    754
      function toInt176(int256 value) internal pure returns (int176 downcasted) {
    755
        downcasted = int176(value);
    756
        if (downcasted != value) {
    757
          revert SafeCastOverflowedIntDowncast(176, value);
    758
        }
    759
      }
    760
    761
      /**
    762
       * @dev Returns the downcasted int168 from int256, reverting on
    763
       * overflow (when the input is less than smallest int168 or
    764
       * greater than largest int168).
    765
       *
    766
       * Counterpart to Solidity's `int168` operator.
    767
       *
    768
       * Requirements:
    769
       *
    770
       * - input must fit into 168 bits
    771
       */
    772
      function toInt168(int256 value) internal pure returns (int168 downcasted) {
    773
        downcasted = int168(value);
    774
        if (downcasted != value) {
    775
          revert SafeCastOverflowedIntDowncast(168, value);
    776
        }
    777
      }
    778
    779
      /**
    780
       * @dev Returns the downcasted int160 from int256, reverting on
    781
       * overflow (when the input is less than smallest int160 or
    782
       * greater than largest int160).
    783
       *
    784
       * Counterpart to Solidity's `int160` operator.
    785
       *
    786
       * Requirements:
    787
       *
    788
       * - input must fit into 160 bits
    789
       */
    790
      function toInt160(int256 value) internal pure returns (int160 downcasted) {
    791
        downcasted = int160(value);
    792
        if (downcasted != value) {
    793
          revert SafeCastOverflowedIntDowncast(160, value);
    794
        }
    795
      }
    796
    797
      /**
    798
       * @dev Returns the downcasted int152 from int256, reverting on
    799
       * overflow (when the input is less than smallest int152 or
    800
       * greater than largest int152).
    801
       *
    802
       * Counterpart to Solidity's `int152` operator.
    803
       *
    804
       * Requirements:
    805
       *
    806
       * - input must fit into 152 bits
    807
       */
    808
      function toInt152(int256 value) internal pure returns (int152 downcasted) {
    809
        downcasted = int152(value);
    810
        if (downcasted != value) {
    811
          revert SafeCastOverflowedIntDowncast(152, value);
    812
        }
    813
      }
    814
    815
      /**
    816
       * @dev Returns the downcasted int144 from int256, reverting on
    817
       * overflow (when the input is less than smallest int144 or
    818
       * greater than largest int144).
    819
       *
    820
       * Counterpart to Solidity's `int144` operator.
    821
       *
    822
       * Requirements:
    823
       *
    824
       * - input must fit into 144 bits
    825
       */
    826
      function toInt144(int256 value) internal pure returns (int144 downcasted) {
    827
        downcasted = int144(value);
    828
        if (downcasted != value) {
    829
          revert SafeCastOverflowedIntDowncast(144, value);
    830
        }
    831
      }
    832
    833
      /**
    834
       * @dev Returns the downcasted int136 from int256, reverting on
    835
       * overflow (when the input is less than smallest int136 or
    836
       * greater than largest int136).
    837
       *
    838
       * Counterpart to Solidity's `int136` operator.
    839
       *
    840
       * Requirements:
    841
       *
    842
       * - input must fit into 136 bits
    843
       */
    844
      function toInt136(int256 value) internal pure returns (int136 downcasted) {
    845
        downcasted = int136(value);
    846
        if (downcasted != value) {
    847
          revert SafeCastOverflowedIntDowncast(136, value);
    848
        }
    849
      }
    850
    851
      /**
    852
       * @dev Returns the downcasted int128 from int256, reverting on
    853
       * overflow (when the input is less than smallest int128 or
    854
       * greater than largest int128).
    855
       *
    856
       * Counterpart to Solidity's `int128` operator.
    857
       *
    858
       * Requirements:
    859
       *
    860
       * - input must fit into 128 bits
    861
       */
    862
      function toInt128(int256 value) internal pure returns (int128 downcasted) {
    863
        downcasted = int128(value);
    864
        if (downcasted != value) {
    865
          revert SafeCastOverflowedIntDowncast(128, value);
    866
        }
    867
      }
    868
    869
      /**
    870
       * @dev Returns the downcasted int120 from int256, reverting on
    871
       * overflow (when the input is less than smallest int120 or
    872
       * greater than largest int120).
    873
       *
    874
       * Counterpart to Solidity's `int120` operator.
    875
       *
    876
       * Requirements:
    877
       *
    878
       * - input must fit into 120 bits
    879
       */
    880
      function toInt120(int256 value) internal pure returns (int120 downcasted) {
    881
        downcasted = int120(value);
    882
        if (downcasted != value) {
    883
          revert SafeCastOverflowedIntDowncast(120, value);
    884
        }
    885
      }
    886
    887
      /**
    888
       * @dev Returns the downcasted int112 from int256, reverting on
    889
       * overflow (when the input is less than smallest int112 or
    890
       * greater than largest int112).
    891
       *
    892
       * Counterpart to Solidity's `int112` operator.
    893
       *
    894
       * Requirements:
    895
       *
    896
       * - input must fit into 112 bits
    897
       */
    898
      function toInt112(int256 value) internal pure returns (int112 downcasted) {
    899
        downcasted = int112(value);
    900
        if (downcasted != value) {
    901
          revert SafeCastOverflowedIntDowncast(112, value);
    902
        }
    903
      }
    904
    905
      /**
    906
       * @dev Returns the downcasted int104 from int256, reverting on
    907
       * overflow (when the input is less than smallest int104 or
    908
       * greater than largest int104).
    909
       *
    910
       * Counterpart to Solidity's `int104` operator.
    911
       *
    912
       * Requirements:
    913
       *
    914
       * - input must fit into 104 bits
    915
       */
    916
      function toInt104(int256 value) internal pure returns (int104 downcasted) {
    917
        downcasted = int104(value);
    918
        if (downcasted != value) {
    919
          revert SafeCastOverflowedIntDowncast(104, value);
    920
        }
    921
      }
    922
    923
      /**
    924
       * @dev Returns the downcasted int96 from int256, reverting on
    925
       * overflow (when the input is less than smallest int96 or
    926
       * greater than largest int96).
    927
       *
    928
       * Counterpart to Solidity's `int96` operator.
    929
       *
    930
       * Requirements:
    931
       *
    932
       * - input must fit into 96 bits
    933
       */
    934
      function toInt96(int256 value) internal pure returns (int96 downcasted) {
    935
        downcasted = int96(value);
    936
        if (downcasted != value) {
    937
          revert SafeCastOverflowedIntDowncast(96, value);
    938
        }
    939
      }
    940
    941
      /**
    942
       * @dev Returns the downcasted int88 from int256, reverting on
    943
       * overflow (when the input is less than smallest int88 or
    944
       * greater than largest int88).
    945
       *
    946
       * Counterpart to Solidity's `int88` operator.
    947
       *
    948
       * Requirements:
    949
       *
    950
       * - input must fit into 88 bits
    951
       */
    952
      function toInt88(int256 value) internal pure returns (int88 downcasted) {
    953
        downcasted = int88(value);
    954
        if (downcasted != value) {
    955
          revert SafeCastOverflowedIntDowncast(88, value);
    956
        }
    957
      }
    958
    959
      /**
    960
       * @dev Returns the downcasted int80 from int256, reverting on
    961
       * overflow (when the input is less than smallest int80 or
    962
       * greater than largest int80).
    963
       *
    964
       * Counterpart to Solidity's `int80` operator.
    965
       *
    966
       * Requirements:
    967
       *
    968
       * - input must fit into 80 bits
    969
       */
    970
      function toInt80(int256 value) internal pure returns (int80 downcasted) {
    971
        downcasted = int80(value);
    972
        if (downcasted != value) {
    973
          revert SafeCastOverflowedIntDowncast(80, value);
    974
        }
    975
      }
    976
    977
      /**
    978
       * @dev Returns the downcasted int72 from int256, reverting on
    979
       * overflow (when the input is less than smallest int72 or
    980
       * greater than largest int72).
    981
       *
    982
       * Counterpart to Solidity's `int72` operator.
    983
       *
    984
       * Requirements:
    985
       *
    986
       * - input must fit into 72 bits
    987
       */
    988
      function toInt72(int256 value) internal pure returns (int72 downcasted) {
    989
        downcasted = int72(value);
    990
        if (downcasted != value) {
    991
          revert SafeCastOverflowedIntDowncast(72, value);
    992
        }
    993
      }
    994
    995
      /**
    996
       * @dev Returns the downcasted int64 from int256, reverting on
    997
       * overflow (when the input is less than smallest int64 or
    998
       * greater than largest int64).
    999
       *
    1000
       * Counterpart to Solidity's `int64` operator.
    1001
       *
    1002
       * Requirements:
    1003
       *
    1004
       * - input must fit into 64 bits
    1005
       */
    1006
      function toInt64(int256 value) internal pure returns (int64 downcasted) {
    1007
        downcasted = int64(value);
    1008
        if (downcasted != value) {
    1009
          revert SafeCastOverflowedIntDowncast(64, value);
    1010
        }
    1011
      }
    1012
    1013
      /**
    1014
       * @dev Returns the downcasted int56 from int256, reverting on
    1015
       * overflow (when the input is less than smallest int56 or
    1016
       * greater than largest int56).
    1017
       *
    1018
       * Counterpart to Solidity's `int56` operator.
    1019
       *
    1020
       * Requirements:
    1021
       *
    1022
       * - input must fit into 56 bits
    1023
       */
    1024
      function toInt56(int256 value) internal pure returns (int56 downcasted) {
    1025
        downcasted = int56(value);
    1026
        if (downcasted != value) {
    1027
          revert SafeCastOverflowedIntDowncast(56, value);
    1028
        }
    1029
      }
    1030
    1031
      /**
    1032
       * @dev Returns the downcasted int48 from int256, reverting on
    1033
       * overflow (when the input is less than smallest int48 or
    1034
       * greater than largest int48).
    1035
       *
    1036
       * Counterpart to Solidity's `int48` operator.
    1037
       *
    1038
       * Requirements:
    1039
       *
    1040
       * - input must fit into 48 bits
    1041
       */
    1042
      function toInt48(int256 value) internal pure returns (int48 downcasted) {
    1043
        downcasted = int48(value);
    1044
        if (downcasted != value) {
    1045
          revert SafeCastOverflowedIntDowncast(48, value);
    1046
        }
    1047
      }
    1048
    1049
      /**
    1050
       * @dev Returns the downcasted int40 from int256, reverting on
    1051
       * overflow (when the input is less than smallest int40 or
    1052
       * greater than largest int40).
    1053
       *
    1054
       * Counterpart to Solidity's `int40` operator.
    1055
       *
    1056
       * Requirements:
    1057
       *
    1058
       * - input must fit into 40 bits
    1059
       */
    1060
      function toInt40(int256 value) internal pure returns (int40 downcasted) {
    1061
        downcasted = int40(value);
    1062
        if (downcasted != value) {
    1063
          revert SafeCastOverflowedIntDowncast(40, value);
    1064
        }
    1065
      }
    1066
    1067
      /**
    1068
       * @dev Returns the downcasted int32 from int256, reverting on
    1069
       * overflow (when the input is less than smallest int32 or
    1070
       * greater than largest int32).
    1071
       *
    1072
       * Counterpart to Solidity's `int32` operator.
    1073
       *
    1074
       * Requirements:
    1075
       *
    1076
       * - input must fit into 32 bits
    1077
       */
    1078
      function toInt32(int256 value) internal pure returns (int32 downcasted) {
    1079
        downcasted = int32(value);
    1080
        if (downcasted != value) {
    1081
          revert SafeCastOverflowedIntDowncast(32, value);
    1082
        }
    1083
      }
    1084
    1085
      /**
    1086
       * @dev Returns the downcasted int24 from int256, reverting on
    1087
       * overflow (when the input is less than smallest int24 or
    1088
       * greater than largest int24).
    1089
       *
    1090
       * Counterpart to Solidity's `int24` operator.
    1091
       *
    1092
       * Requirements:
    1093
       *
    1094
       * - input must fit into 24 bits
    1095
       */
    1096
      function toInt24(int256 value) internal pure returns (int24 downcasted) {
    1097
        downcasted = int24(value);
    1098
        if (downcasted != value) {
    1099
          revert SafeCastOverflowedIntDowncast(24, value);
    1100
        }
    1101
      }
    1102
    1103
      /**
    1104
       * @dev Returns the downcasted int16 from int256, reverting on
    1105
       * overflow (when the input is less than smallest int16 or
    1106
       * greater than largest int16).
    1107
       *
    1108
       * Counterpart to Solidity's `int16` operator.
    1109
       *
    1110
       * Requirements:
    1111
       *
    1112
       * - input must fit into 16 bits
    1113
       */
    1114
      function toInt16(int256 value) internal pure returns (int16 downcasted) {
    1115
        downcasted = int16(value);
    1116
        if (downcasted != value) {
    1117
          revert SafeCastOverflowedIntDowncast(16, value);
    1118
        }
    1119
      }
    1120
    1121
      /**
    1122
       * @dev Returns the downcasted int8 from int256, reverting on
    1123
       * overflow (when the input is less than smallest int8 or
    1124
       * greater than largest int8).
    1125
       *
    1126
       * Counterpart to Solidity's `int8` operator.
    1127
       *
    1128
       * Requirements:
    1129
       *
    1130
       * - input must fit into 8 bits
    1131
       */
    1132
      function toInt8(int256 value) internal pure returns (int8 downcasted) {
    1133
        downcasted = int8(value);
    1134
        if (downcasted != value) {
    1135
          revert SafeCastOverflowedIntDowncast(8, value);
    1136
        }
    1137
      }
    1138
    1139
      /**
    1140
       * @dev Converts an unsigned uint256 into a signed int256.
    1141
       *
    1142
       * Requirements:
    1143
       *
    1144
       * - input must be less than or equal to maxInt256.
    1145
       */
    1146 10×
      function toInt256(uint256 value) internal pure returns (int256) {
    1147
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
    1148 25×
        if (value > uint256(type(int256).max)) {
    1149 0
          revert SafeCastOverflowedUintToInt(value);
    1150
        }
    1151 0
        return int256(value);
    1152
      }
    1153
    1154
      /**
    1155
       * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
    1156
       */
    1157 0
      function toUint(bool b) internal pure returns (uint256 u) {
    1158
        assembly ('memory-safe') {
    1159 0
          u := iszero(iszero(b))
    1160
        }
    1161
      }
    1162
    }
    50% src/dependencies/openzeppelin/SafeERC20.sol
    Lines covered: 15 / 30 (50%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {IERC20} from './IERC20.sol';
    7
    import {IERC1363} from './IERC1363.sol';
    8
    9
    /**
    10
     * @title SafeERC20
    11
     * @dev Wrappers around ERC-20 operations that throw on failure (when the token
    12
     * contract returns false). Tokens that return no value (and instead revert or
    13
     * throw on failure) are also supported, non-reverting calls are assumed to be
    14
     * successful.
    15
     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
    16
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
    17
     */
    18 0
    library SafeERC20 {
    19
      /**
    20
       * @dev An operation with an ERC-20 token failed.
    21
       */
    22
      error SafeERC20FailedOperation(address token);
    23
    24
      /**
    25
       * @dev Indicates a failed `decreaseAllowance` request.
    26
       */
    27
      error SafeERC20FailedDecreaseAllowance(
    28
        address spender,
    29
        uint256 currentAllowance,
    30
        uint256 requestedDecrease
    31
      );
    32
    33
      /**
    34
       * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
    35
       * non-reverting calls are assumed to be successful.
    36
       */
    37
      function safeTransfer(IERC20 token, address to, uint256 value) internal {
    38 98×
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    39
      }
    40
    41
      /**
    42
       * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
    43
       * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
    44
       */
    45
      function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
    46 69×
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    47
      }
    48
    49
      /**
    50
       * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
    51
       */
    52
      function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
    53
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    54
      }
    55
    56
      /**
    57
       * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
    58
       */
    59
      function trySafeTransferFrom(
    60
        IERC20 token,
    61
        address from,
    62
        address to,
    63
        uint256 value
    64
      ) internal returns (bool) {
    65
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    66
      }
    67
    68
      /**
    69
       * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
    70
       * non-reverting calls are assumed to be successful.
    71
       *
    72
       * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
    73
       * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
    74
       * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
    75
       * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
    76
       */
    77
      function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    78
        uint256 oldAllowance = token.allowance(address(this), spender);
    79
        forceApprove(token, spender, oldAllowance + value);
    80
      }
    81
    82
      /**
    83
       * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
    84
       * value, non-reverting calls are assumed to be successful.
    85
       *
    86
       * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
    87
       * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
    88
       * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
    89
       * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
    90
       */
    91
      function safeDecreaseAllowance(
    92
        IERC20 token,
    93
        address spender,
    94
        uint256 requestedDecrease
    95
      ) internal {
    96
        unchecked {
    97
          uint256 currentAllowance = token.allowance(address(this), spender);
    98
          if (currentAllowance < requestedDecrease) {
    99
            revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
    100
          }
    101
          forceApprove(token, spender, currentAllowance - requestedDecrease);
    102
        }
    103
      }
    104
    105
      /**
    106
       * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
    107
       * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
    108
       * to be set to zero before setting it to a non-zero value, such as USDT.
    109
       *
    110
       * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
    111
       * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
    112
       * set here.
    113
       */
    114 0
      function forceApprove(IERC20 token, address spender, uint256 value) internal {
    115 0
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
    116
    117 0
        if (!_callOptionalReturnBool(token, approvalCall)) {
    118 0
          _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
    119 0
          _callOptionalReturn(token, approvalCall);
    120
        }
    121
      }
    122
    123
      /**
    124
       * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
    125
       * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
    126
       * targeting contracts.
    127
       *
    128
       * Reverts if the returned value is other than `true`.
    129
       */
    130
      function transferAndCallRelaxed(
    131
        IERC1363 token,
    132
        address to,
    133
        uint256 value,
    134
        bytes memory data
    135
      ) internal {
    136
        if (to.code.length == 0) {
    137
          safeTransfer(token, to, value);
    138
        } else if (!token.transferAndCall(to, value, data)) {
    139
          revert SafeERC20FailedOperation(address(token));
    140
        }
    141
      }
    142
    143
      /**
    144
       * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
    145
       * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
    146
       * targeting contracts.
    147
       *
    148
       * Reverts if the returned value is other than `true`.
    149
       */
    150
      function transferFromAndCallRelaxed(
    151
        IERC1363 token,
    152
        address from,
    153
        address to,
    154
        uint256 value,
    155
        bytes memory data
    156
      ) internal {
    157
        if (to.code.length == 0) {
    158
          safeTransferFrom(token, from, to, value);
    159
        } else if (!token.transferFromAndCall(from, to, value, data)) {
    160
          revert SafeERC20FailedOperation(address(token));
    161
        }
    162
      }
    163
    164
      /**
    165
       * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
    166
       * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
    167
       * targeting contracts.
    168
       *
    169
       * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
    170
       * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
    171
       * once without retrying, and relies on the returned value to be true.
    172
       *
    173
       * Reverts if the returned value is other than `true`.
    174
       */
    175
      function approveAndCallRelaxed(
    176
        IERC1363 token,
    177
        address to,
    178
        uint256 value,
    179
        bytes memory data
    180
      ) internal {
    181
        if (to.code.length == 0) {
    182
          forceApprove(token, to, value);
    183
        } else if (!token.approveAndCall(to, value, data)) {
    184
          revert SafeERC20FailedOperation(address(token));
    185
        }
    186
      }
    187
    188
      /**
    189
       * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    190
       * on the return value: the return value is optional (but if data is returned, it must not be false).
    191
       * @param token The token targeted by the call.
    192
       * @param data The call data (encoded using abi.encode or one of its variants).
    193
       *
    194
       * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
    195
       */
    196
      function _callOptionalReturn(IERC20 token, bytes memory data) private {
    197
        uint256 returnSize;
    198
        uint256 returnValue;
    199
        assembly ('memory-safe') {
    200 44×
          let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
    201
          // bubble errors
    202 16×
          if iszero(success) {
    203
            let ptr := mload(0x40)
    204
            returndatacopy(ptr, 0, returndatasize())
    205
            revert(ptr, returndatasize())
    206
          }
    207
          returnSize := returndatasize()
    208
          returnValue := mload(0)
    209
        }
    210
    211 56×
        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
    212 0
          revert SafeERC20FailedOperation(address(token));
    213
        }
    214
      }
    215
    216
      /**
    217
       * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    218
       * on the return value: the return value is optional (but if data is returned, it must not be false).
    219
       * @param token The token targeted by the call.
    220
       * @param data The call data (encoded using abi.encode or one of its variants).
    221
       *
    222
       * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
    223
       */
    224 0
      function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
    225 0
        bool success;
    226 0
        uint256 returnSize;
    227 0
        uint256 returnValue;
    228
        assembly ('memory-safe') {
    229 0
          success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
    230 0
          returnSize := returndatasize()
    231 0
          returnValue := mload(0)
    232
        }
    233 0
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    234
      }
    235
    }
    0% src/dependencies/openzeppelin/SignatureChecker.sol
    Lines covered: 0 / 14 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/SignatureChecker.sol)
    3
    4
    pragma solidity ^0.8.24;
    5
    6
    import {ECDSA} from './ECDSA.sol';
    7
    import {IERC1271} from './IERC1271.sol';
    8
    import {IERC7913SignatureVerifier} from './IERC7913.sol';
    9
    import {Bytes} from './Bytes.sol';
    10
    11
    /**
    12
     * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support:
    13
     *
    14
     * * ECDSA signatures from externally owned accounts (EOAs)
    15
     * * ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet (previously Gnosis Safe)
    16
     * * ERC-7913 signatures from keys that do not have an Ethereum address of their own
    17
     *
    18
     * See https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] and https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
    19
     */
    20 0
    library SignatureChecker {
    21
      using Bytes for bytes;
    22
    23
      /**
    24
       * @dev Checks if a signature is valid for a given signer and data hash. If the signer has code, the
    25
       * signature is validated against it using ERC-1271, otherwise it's validated using `ECDSA.recover`.
    26
       *
    27
       * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
    28
       * change through time. It could return true at block N and false at block N+1 (or the opposite).
    29
       *
    30
       * NOTE: For an extended version of this function that supports ERC-7913 signatures, see {isValidSignatureNow-bytes-bytes32-bytes-}.
    31
       */
    32 0
      function isValidSignatureNow(
    33
        address signer,
    34
        bytes32 hash,
    35
        bytes memory signature
    36 0
      ) internal view returns (bool) {
    37 0
        if (signer.code.length == 0) {
    38 0
          (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
    39 0
          return err == ECDSA.RecoverError.NoError && recovered == signer;
    40
        } else {
    41 0
          return isValidERC1271SignatureNow(signer, hash, signature);
    42
        }
    43
      }
    44
    45
      /**
    46
       * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
    47
       * against the signer smart contract using ERC-1271.
    48
       *
    49
       * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
    50
       * change through time. It could return true at block N and false at block N+1 (or the opposite).
    51
       */
    52 0
      function isValidERC1271SignatureNow(
    53
        address signer,
    54
        bytes32 hash,
    55
        bytes memory signature
    56 0
      ) internal view returns (bool) {
    57 0
        (bool success, bytes memory result) = signer.staticcall(
    58 0
          abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
    59
        );
    60 0
        return (success &&
    61 0
          result.length >= 32 &&
    62 0
          abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
    63
      }
    64
    65
      /**
    66
       * @dev Verifies a signature for a given ERC-7913 signer and hash.
    67
       *
    68
       * The signer is a `bytes` object that is the concatenation of an address and optionally a key:
    69
       * `verifier || key`. A signer must be at least 20 bytes long.
    70
       *
    71
       * Verification is done as follows:
    72
       *
    73
       * * If `signer.length < 20`: verification fails
    74
       * * If `signer.length == 20`: verification is done using {isValidSignatureNow}
    75
       * * Otherwise: verification is done using {IERC7913SignatureVerifier}
    76
       *
    77
       * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
    78
       * change through time. It could return true at block N and false at block N+1 (or the opposite).
    79
       */
    80
      function isValidSignatureNow(
    81
        bytes memory signer,
    82
        bytes32 hash,
    83
        bytes memory signature
    84
      ) internal view returns (bool) {
    85
        if (signer.length < 20) {
    86
          return false;
    87
        } else if (signer.length == 20) {
    88
          return isValidSignatureNow(address(bytes20(signer)), hash, signature);
    89
        } else {
    90
          (bool success, bytes memory result) = address(bytes20(signer)).staticcall(
    91
            abi.encodeCall(IERC7913SignatureVerifier.verify, (signer.slice(20), hash, signature))
    92
          );
    93
          return (success &&
    94
            result.length >= 32 &&
    95
            abi.decode(result, (bytes32)) == bytes32(IERC7913SignatureVerifier.verify.selector));
    96
        }
    97
      }
    98
    99
      /**
    100
       * @dev Verifies multiple ERC-7913 `signatures` for a given `hash` using a set of `signers`.
    101
       * Returns `false` if the number of signers and signatures is not the same.
    102
       *
    103
       * The signers should be ordered by their `keccak256` hash to ensure efficient duplication check. Unordered
    104
       * signers are supported, but the uniqueness check will be more expensive.
    105
       *
    106
       * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
    107
       * change through time. It could return true at block N and false at block N+1 (or the opposite).
    108
       */
    109
      function areValidSignaturesNow(
    110
        bytes32 hash,
    111
        bytes[] memory signers,
    112
        bytes[] memory signatures
    113
      ) internal view returns (bool) {
    114
        if (signers.length != signatures.length) return false;
    115
    116
        bytes32 lastId = bytes32(0);
    117
    118
        for (uint256 i = 0; i < signers.length; ++i) {
    119
          bytes memory signer = signers[i];
    120
    121
          // If one of the signatures is invalid, reject the batch
    122
          if (!isValidSignatureNow(signer, hash, signatures[i])) return false;
    123
    124
          bytes32 id = keccak256(signer);
    125
          // If the current signer ID is greater than all previous IDs, then this is a new signer.
    126
          if (lastId < id) {
    127
            lastId = id;
    128
          } else {
    129
            // If this signer id is not greater than all the previous ones, verify that it is not a duplicate of a previous one
    130
            // This loop is never executed if the signers are ordered by id.
    131
            for (uint256 j = 0; j < i; ++j) {
    132
              if (id == keccak256(signers[j])) return false;
    133
            }
    134
          }
    135
        }
    136
    137
        return true;
    138
      }
    139
    }
    0% src/dependencies/openzeppelin/SlotDerivation.sol
    Lines covered: 0 / 6 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/SlotDerivation.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.
    4
    5
    pragma solidity ^0.8.20;
    6
    7
    /**
    8
     * @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
    9
     * corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
    10
     * the solidity language / compiler.
    11
     *
    12
     * See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
    13
     *
    14
     * Example usage:
    15
     * ```solidity
    16
     * contract Example {
    17
     *     // Add the library methods
    18
     *     using StorageSlot for bytes32;
    19
     *     using SlotDerivation for bytes32;
    20
     *
    21
     *     // Declare a namespace
    22
     *     string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot
    23
     *
    24
     *     function setValueInNamespace(uint256 key, address newValue) internal {
    25
     *         _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
    26
     *     }
    27
     *
    28
     *     function getValueInNamespace(uint256 key) internal view returns (address) {
    29
     *         return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
    30
     *     }
    31
     * }
    32
     * ```
    33
     *
    34
     * TIP: Consider using this library along with {StorageSlot}.
    35
     *
    36
     * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
    37
     * upgrade safety will ignore the slots accessed through this library.
    38
     *
    39
     * _Available since v5.1._
    40
     */
    41 0
    library SlotDerivation {
    42
      /**
    43
       * @dev Derive an ERC-7201 slot from a string (namespace).
    44
       */
    45
      function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
    46
        assembly ('memory-safe') {
    47
          mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
    48
          slot := and(keccak256(0x00, 0x20), not(0xff))
    49
        }
    50
      }
    51
    52
      /**
    53
       * @dev Add an offset to a slot to get the n-th element of a structure or an array.
    54
       */
    55 0
      function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
    56
        unchecked {
    57 0
          return bytes32(uint256(slot) + pos);
    58
        }
    59
      }
    60
    61
      /**
    62
       * @dev Derive the location of the first element in an array from the slot where the length is stored.
    63
       */
    64 0
      function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
    65
        assembly ('memory-safe') {
    66 0
          mstore(0x00, slot)
    67 0
          result := keccak256(0x00, 0x20)
    68
        }
    69
      }
    70
    71
      /**
    72
       * @dev Derive the location of a mapping element from the key.
    73
       */
    74
      function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
    75
        assembly ('memory-safe') {
    76
          mstore(0x00, and(key, shr(96, not(0))))
    77
          mstore(0x20, slot)
    78
          result := keccak256(0x00, 0x40)
    79
        }
    80
      }
    81
    82
      /**
    83
       * @dev Derive the location of a mapping element from the key.
    84
       */
    85
      function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
    86
        assembly ('memory-safe') {
    87
          mstore(0x00, iszero(iszero(key)))
    88
          mstore(0x20, slot)
    89
          result := keccak256(0x00, 0x40)
    90
        }
    91
      }
    92
    93
      /**
    94
       * @dev Derive the location of a mapping element from the key.
    95
       */
    96
      function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
    97
        assembly ('memory-safe') {
    98
          mstore(0x00, key)
    99
          mstore(0x20, slot)
    100
          result := keccak256(0x00, 0x40)
    101
        }
    102
      }
    103
    104
      /**
    105
       * @dev Derive the location of a mapping element from the key.
    106
       */
    107
      function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
    108
        assembly ('memory-safe') {
    109
          mstore(0x00, key)
    110
          mstore(0x20, slot)
    111
          result := keccak256(0x00, 0x40)
    112
        }
    113
      }
    114
    115
      /**
    116
       * @dev Derive the location of a mapping element from the key.
    117
       */
    118
      function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
    119
        assembly ('memory-safe') {
    120
          mstore(0x00, key)
    121
          mstore(0x20, slot)
    122
          result := keccak256(0x00, 0x40)
    123
        }
    124
      }
    125
    126
      /**
    127
       * @dev Derive the location of a mapping element from the key.
    128
       */
    129
      function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
    130
        assembly ('memory-safe') {
    131
          let length := mload(key)
    132
          let begin := add(key, 0x20)
    133
          let end := add(begin, length)
    134
          let cache := mload(end)
    135
          mstore(end, slot)
    136
          result := keccak256(begin, add(length, 0x20))
    137
          mstore(end, cache)
    138
        }
    139
      }
    140
    141
      /**
    142
       * @dev Derive the location of a mapping element from the key.
    143
       */
    144
      function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
    145
        assembly ('memory-safe') {
    146
          let length := mload(key)
    147
          let begin := add(key, 0x20)
    148
          let end := add(begin, length)
    149
          let cache := mload(end)
    150
          mstore(end, slot)
    151
          result := keccak256(begin, add(length, 0x20))
    152
          mstore(end, cache)
    153
        }
    154
      }
    155
    }
    0% src/dependencies/openzeppelin/StorageSlot.sol
    Lines covered: 0 / 4 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    4
    5
    pragma solidity ^0.8.20;
    6
    7
    /**
    8
     * @dev Library for reading and writing primitive types to specific storage slots.
    9
     *
    10
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
    11
     * This library helps with reading and writing to such slots without the need for inline assembly.
    12
     *
    13
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
    14
     *
    15
     * Example usage to set ERC-1967 implementation slot:
    16
     * ```solidity
    17
     * contract ERC1967 {
    18
     *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
    19
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    20
     *
    21
     *     function _getImplementation() internal view returns (address) {
    22
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    23
     *     }
    24
     *
    25
     *     function _setImplementation(address newImplementation) internal {
    26
     *         require(newImplementation.code.length > 0);
    27
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    28
     *     }
    29
     * }
    30
     * ```
    31
     *
    32
     * TIP: Consider using this library along with {SlotDerivation}.
    33
     */
    34 0
    library StorageSlot {
    35
      struct AddressSlot {
    36
        address value;
    37
      }
    38
    39
      struct BooleanSlot {
    40
        bool value;
    41
      }
    42
    43
      struct Bytes32Slot {
    44
        bytes32 value;
    45
      }
    46
    47
      struct Uint256Slot {
    48
        uint256 value;
    49
      }
    50
    51
      struct Int256Slot {
    52
        int256 value;
    53
      }
    54
    55
      struct StringSlot {
    56
        string value;
    57
      }
    58
    59
      struct BytesSlot {
    60
        bytes value;
    61
      }
    62
    63
      /**
    64
       * @dev Returns an `AddressSlot` with member `value` located at `slot`.
    65
       */
    66 0
      function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
    67
        assembly ('memory-safe') {
    68
          r.slot := slot
    69
        }
    70
      }
    71
    72
      /**
    73
       * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
    74
       */
    75
      function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
    76
        assembly ('memory-safe') {
    77
          r.slot := slot
    78
        }
    79
      }
    80
    81
      /**
    82
       * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
    83
       */
    84 0
      function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
    85
        assembly ('memory-safe') {
    86 0
          r.slot := slot
    87
        }
    88
      }
    89
    90
      /**
    91
       * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
    92
       */
    93
      function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
    94
        assembly ('memory-safe') {
    95
          r.slot := slot
    96
        }
    97
      }
    98
    99
      /**
    100
       * @dev Returns a `Int256Slot` with member `value` located at `slot`.
    101
       */
    102
      function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
    103
        assembly ('memory-safe') {
    104
          r.slot := slot
    105
        }
    106
      }
    107
    108
      /**
    109
       * @dev Returns a `StringSlot` with member `value` located at `slot`.
    110
       */
    111
      function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
    112
        assembly ('memory-safe') {
    113
          r.slot := slot
    114
        }
    115
      }
    116
    117
      /**
    118
       * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
    119
       */
    120
      function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
    121
        assembly ('memory-safe') {
    122
          r.slot := store.slot
    123
        }
    124
      }
    125
    126
      /**
    127
       * @dev Returns a `BytesSlot` with member `value` located at `slot`.
    128
       */
    129
      function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
    130
        assembly ('memory-safe') {
    131
          r.slot := slot
    132
        }
    133
      }
    134
    135
      /**
    136
       * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
    137
       */
    138
      function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
    139
        assembly ('memory-safe') {
    140
          r.slot := store.slot
    141
        }
    142
      }
    143
    }
    54% src/dependencies/openzeppelin/Time.sol
    Lines covered: 12 / 22 (54%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/types/Time.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Math} from './Math.sol';
    7
    import {SafeCast} from './SafeCast.sol';
    8
    9
    /**
    10
     * @dev This library provides helpers for manipulating time-related objects.
    11
     *
    12
     * It uses the following types:
    13
     * - `uint48` for timepoints
    14
     * - `uint32` for durations
    15
     *
    16
     * While the library doesn't provide specific types for timepoints and duration, it does provide:
    17
     * - a `Delay` type to represent duration that can be programmed to change value automatically at a given point
    18
     * - additional helper functions
    19
     */
    20 0
    library Time {
    21
      using Time for *;
    22
    23
      /**
    24
       * @dev Get the block timestamp as a Timepoint.
    25
       */
    26 12×
      function timestamp() internal view returns (uint48) {
    27 21×
        return SafeCast.toUint48(block.timestamp);
    28
      }
    29
    30
      /**
    31
       * @dev Get the block number as a Timepoint.
    32
       */
    33
      function blockNumber() internal view returns (uint48) {
    34
        return SafeCast.toUint48(block.number);
    35
      }
    36
    37
      // ==================================================== Delay =====================================================
    38
      /**
    39
       * @dev A `Delay` is a uint32 duration that can be programmed to change value automatically at a given point in the
    40
       * future. The "effect" timepoint describes when the transitions happens from the "old" value to the "new" value.
    41
       * This allows updating the delay applied to some operation while keeping some guarantees.
    42
       *
    43
       * In particular, the {update} function guarantees that if the delay is reduced, the old delay still applies for
    44
       * some time. For example if the delay is currently 7 days to do an upgrade, the admin should not be able to set
    45
       * the delay to 0 and upgrade immediately. If the admin wants to reduce the delay, the old delay (7 days) should
    46
       * still apply for some time.
    47
       *
    48
       *
    49
       * The `Delay` type is 112 bits long, and packs the following:
    50
       *
    51
       * ```
    52
       *   | [uint48]: effect date (timepoint)
    53
       *   |           | [uint32]: value before (duration)
    54
       *   ↓           ↓       ↓ [uint32]: value after (duration)
    55
       * 0xAAAAAAAAAAAABBBBBBBBCCCCCCCC
    56
       * ```
    57
       *
    58
       * NOTE: The {get} and {withUpdate} functions operate using timestamps. Block number based delays are not currently
    59
       * supported.
    60
       */
    61
      type Delay is uint112;
    62
    63
      /**
    64
       * @dev Wrap a duration into a Delay to add the one-step "update in the future" feature
    65
       */
    66
      function toDelay(uint32 duration) internal pure returns (Delay) {
    67
        return Delay.wrap(duration);
    68
      }
    69
    70
      /**
    71
       * @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled
    72
       * change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered.
    73
       */
    74 14×
      function _getFullAt(
    75
        Delay self,
    76
        uint48 timepoint
    77
      ) private pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) {
    78
        (valueBefore, valueAfter, effect) = self.unpack();
    79 36×
        return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect);
    80
      }
    81
    82
      /**
    83
       * @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the
    84
       * effect timepoint is 0, then the pending value should not be considered.
    85
       */
    86 14×
      function getFull(
    87
        Delay self
    88
      ) internal view returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) {
    89 30×
        return _getFullAt(self, timestamp());
    90
      }
    91
    92
      /**
    93
       * @dev Get the current value.
    94
       */
    95 0
      function get(Delay self) internal view returns (uint32) {
    96 0
        (uint32 delay, , ) = self.getFull();
    97
        return delay;
    98
      }
    99
    100
      /**
    101
       * @dev Update a Delay object so that it takes a new duration after a timepoint that is automatically computed to
    102
       * enforce the old delay at the moment of the update. Returns the updated Delay object and the timestamp when the
    103
       * new delay becomes effective.
    104
       */
    105 0
      function withUpdate(
    106
        Delay self,
    107
        uint32 newValue,
    108
        uint32 minSetback
    109 0
      ) internal view returns (Delay updatedDelay, uint48 effect) {
    110 0
        uint32 value = self.get();
    111 0
        uint32 setback = uint32(Math.max(minSetback, value > newValue ? value - newValue : 0));
    112 0
        effect = timestamp() + setback;
    113 0
        return (pack(value, newValue, effect), effect);
    114
      }
    115
    116
      /**
    117
       * @dev Split a delay into its components: valueBefore, valueAfter and effect (transition timepoint).
    118
       */
    119
      function unpack(
    120
        Delay self
    121
      ) internal pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) {
    122
        uint112 raw = Delay.unwrap(self);
    123
    124
        valueAfter = uint32(raw);
    125 12×
        valueBefore = uint32(raw >> 32);
    126 16×
        effect = uint48(raw >> 64);
    127
    128
        return (valueBefore, valueAfter, effect);
    129
      }
    130
    131
      /**
    132
       * @dev pack the components into a Delay object.
    133
       */
    134
      function pack(
    135
        uint32 valueBefore,
    136
        uint32 valueAfter,
    137
        uint48 effect
    138
      ) internal pure returns (Delay) {
    139 0
        return Delay.wrap((uint112(effect) << 64) | (uint112(valueBefore) << 32) | uint112(valueAfter));
    140
      }
    141
    }
    0% src/dependencies/openzeppelin/TransientSlot.sol
    Lines covered: 0 / 5 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/TransientSlot.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/TransientSlot.js.
    4
    5
    pragma solidity ^0.8.24;
    6
    7
    /**
    8
     * @dev Library for reading and writing value-types to specific transient storage slots.
    9
     *
    10
     * Transient slots are often used to store temporary values that are removed after the current transaction.
    11
     * This library helps with reading and writing to such slots without the need for inline assembly.
    12
     *
    13
     *  * Example reading and writing values using transient storage:
    14
     * ```solidity
    15
     * contract Lock {
    16
     *     using TransientSlot for *;
    17
     *
    18
     *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
    19
     *     bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
    20
     *
    21
     *     modifier locked() {
    22
     *         require(!_LOCK_SLOT.asBoolean().tload());
    23
     *
    24
     *         _LOCK_SLOT.asBoolean().tstore(true);
    25
     *         _;
    26
     *         _LOCK_SLOT.asBoolean().tstore(false);
    27
     *     }
    28
     * }
    29
     * ```
    30
     *
    31
     * TIP: Consider using this library along with {SlotDerivation}.
    32
     */
    33 0
    library TransientSlot {
    34
      /**
    35
       * @dev UDVT that represents a slot holding an address.
    36
       */
    37
      type AddressSlot is bytes32;
    38
    39
      /**
    40
       * @dev Cast an arbitrary slot to a AddressSlot.
    41
       */
    42
      function asAddress(bytes32 slot) internal pure returns (AddressSlot) {
    43
        return AddressSlot.wrap(slot);
    44
      }
    45
    46
      /**
    47
       * @dev UDVT that represents a slot holding a bool.
    48
       */
    49
      type BooleanSlot is bytes32;
    50
    51
      /**
    52
       * @dev Cast an arbitrary slot to a BooleanSlot.
    53
       */
    54 0
      function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) {
    55
        return BooleanSlot.wrap(slot);
    56
      }
    57
    58
      /**
    59
       * @dev UDVT that represents a slot holding a bytes32.
    60
       */
    61
      type Bytes32Slot is bytes32;
    62
    63
      /**
    64
       * @dev Cast an arbitrary slot to a Bytes32Slot.
    65
       */
    66
      function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) {
    67
        return Bytes32Slot.wrap(slot);
    68
      }
    69
    70
      /**
    71
       * @dev UDVT that represents a slot holding a uint256.
    72
       */
    73
      type Uint256Slot is bytes32;
    74
    75
      /**
    76
       * @dev Cast an arbitrary slot to a Uint256Slot.
    77
       */
    78
      function asUint256(bytes32 slot) internal pure returns (Uint256Slot) {
    79
        return Uint256Slot.wrap(slot);
    80
      }
    81
    82
      /**
    83
       * @dev UDVT that represents a slot holding a int256.
    84
       */
    85
      type Int256Slot is bytes32;
    86
    87
      /**
    88
       * @dev Cast an arbitrary slot to a Int256Slot.
    89
       */
    90
      function asInt256(bytes32 slot) internal pure returns (Int256Slot) {
    91
        return Int256Slot.wrap(slot);
    92
      }
    93
    94
      /**
    95
       * @dev Load the value held at location `slot` in transient storage.
    96
       */
    97
      function tload(AddressSlot slot) internal view returns (address value) {
    98
        assembly ('memory-safe') {
    99
          value := tload(slot)
    100
        }
    101
      }
    102
    103
      /**
    104
       * @dev Store `value` at location `slot` in transient storage.
    105
       */
    106
      function tstore(AddressSlot slot, address value) internal {
    107
        assembly ('memory-safe') {
    108
          tstore(slot, value)
    109
        }
    110
      }
    111
    112
      /**
    113
       * @dev Load the value held at location `slot` in transient storage.
    114
       */
    115
      function tload(BooleanSlot slot) internal view returns (bool value) {
    116
        assembly ('memory-safe') {
    117 0
          value := tload(slot)
    118
        }
    119
      }
    120
    121
      /**
    122
       * @dev Store `value` at location `slot` in transient storage.
    123
       */
    124 0
      function tstore(BooleanSlot slot, bool value) internal {
    125
        assembly ('memory-safe') {
    126 0
          tstore(slot, value)
    127
        }
    128
      }
    129
    130
      /**
    131
       * @dev Load the value held at location `slot` in transient storage.
    132
       */
    133
      function tload(Bytes32Slot slot) internal view returns (bytes32 value) {
    134
        assembly ('memory-safe') {
    135
          value := tload(slot)
    136
        }
    137
      }
    138
    139
      /**
    140
       * @dev Store `value` at location `slot` in transient storage.
    141
       */
    142
      function tstore(Bytes32Slot slot, bytes32 value) internal {
    143
        assembly ('memory-safe') {
    144
          tstore(slot, value)
    145
        }
    146
      }
    147
    148
      /**
    149
       * @dev Load the value held at location `slot` in transient storage.
    150
       */
    151
      function tload(Uint256Slot slot) internal view returns (uint256 value) {
    152
        assembly ('memory-safe') {
    153
          value := tload(slot)
    154
        }
    155
      }
    156
    157
      /**
    158
       * @dev Store `value` at location `slot` in transient storage.
    159
       */
    160
      function tstore(Uint256Slot slot, uint256 value) internal {
    161
        assembly ('memory-safe') {
    162
          tstore(slot, value)
    163
        }
    164
      }
    165
    166
      /**
    167
       * @dev Load the value held at location `slot` in transient storage.
    168
       */
    169
      function tload(Int256Slot slot) internal view returns (int256 value) {
    170
        assembly ('memory-safe') {
    171
          value := tload(slot)
    172
        }
    173
      }
    174
    175
      /**
    176
       * @dev Store `value` at location `slot` in transient storage.
    177
       */
    178
      function tstore(Int256Slot slot, int256 value) internal {
    179
        assembly ('memory-safe') {
    180
          tstore(slot, value)
    181
        }
    182
      }
    183
    }
    0% src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol
    Lines covered: 0 / 16 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
    3
    4
    pragma solidity ^0.8.22;
    5
    6
    import {ERC1967Utils} from './ERC1967Utils.sol';
    7
    import {ERC1967Proxy} from './ERC1967Proxy.sol';
    8
    import {IERC1967} from './IERC1967.sol';
    9
    import {ProxyAdmin} from './ProxyAdmin.sol';
    10
    11
    /**
    12
     * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
    13
     * does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch
    14
     * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
    15
     * include them in the ABI so this interface must be used to interact with it.
    16
     */
    17
    interface ITransparentUpgradeableProxy is IERC1967 {
    18
      /// @dev See {UUPSUpgradeable-upgradeToAndCall}
    19
      function upgradeToAndCall(address newImplementation, bytes calldata data) external payable;
    20
    }
    21
    22
    /**
    23
     * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
    24
     *
    25
     * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
    26
     * clashing], which can potentially be used in an attack, this contract uses the
    27
     * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
    28
     * things that go hand in hand:
    29
     *
    30
     * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
    31
     * that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
    32
     * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
    33
     * the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
    34
     * the proxy admin cannot fallback to the target implementation.
    35
     *
    36
     * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a
    37
     * dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to
    38
     * call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and
    39
     * allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
    40
     * interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
    41
     *
    42
     * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
    43
     * inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch
    44
     * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
    45
     * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
    46
     * implementation.
    47
     *
    48
     * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
    49
     * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
    50
     *
    51
     * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
    52
     * immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
    53
     * overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
    54
     * undesirable state where the admin slot is different from the actual admin. Relying on the value of the admin slot
    55
     * is generally fine if the implementation is trusted.
    56
     *
    57
     * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the
    58
     * compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new
    59
     * function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This
    60
     * could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
    61
     */
    62 0
    contract TransparentUpgradeableProxy is ERC1967Proxy {
    63
      // An immutable address for the admin to avoid unnecessary SLOADs before each call
    64
      // at the expense of removing the ability to change the admin once it's set.
    65
      // This is acceptable if the admin is always a ProxyAdmin instance or similar contract
    66
      // with its own ability to transfer the permissions to another account.
    67
      address private immutable _admin;
    68
    69
      /**
    70
       * @dev The proxy caller is the current admin, and can't fallback to the proxy target.
    71
       */
    72
      error ProxyDeniedAdminAccess();
    73
    74
      /**
    75
       * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
    76
       * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
    77
       * {ERC1967Proxy-constructor}.
    78
       */
    79 0
      constructor(
    80
        address _logic,
    81
        address initialOwner,
    82
        bytes memory _data
    83 0
      ) payable ERC1967Proxy(_logic, _data) {
    84 0
        _admin = address(new ProxyAdmin(initialOwner));
    85
        // Set the storage value and emit an event for ERC-1967 compatibility
    86 0
        ERC1967Utils.changeAdmin(_proxyAdmin());
    87
      }
    88
    89
      /**
    90
       * @dev Returns the admin of this proxy.
    91
       */
    92 0
      function _proxyAdmin() internal view virtual returns (address) {
    93 0
        return _admin;
    94
      }
    95
    96
      /**
    97
       * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
    98
       */
    99 0
      function _fallback() internal virtual override {
    100 0
        if (msg.sender == _proxyAdmin()) {
    101 0
          if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
    102 0
            revert ProxyDeniedAdminAccess();
    103
          } else {
    104 0
            _dispatchUpgradeToAndCall();
    105
          }
    106
        } else {
    107 0
          super._fallback();
    108
        }
    109
      }
    110
    111
      /**
    112
       * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
    113
       *
    114
       * Requirements:
    115
       *
    116
       * - If `data` is empty, `msg.value` must be zero.
    117
       */
    118 0
      function _dispatchUpgradeToAndCall() private {
    119 0
        (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
    120 0
        ERC1967Utils.upgradeToAndCall(newImplementation, data);
    121
      }
    122
    }
    47% src/dependencies/openzeppelin-upgradeable/AccessManagedUpgradeable.sol
    Lines covered: 17 / 36 (47%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (access/manager/AccessManaged.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {AuthorityUtils} from 'src/dependencies/openzeppelin/AuthorityUtils.sol';
    7
    import {IAccessManager} from 'src/dependencies/openzeppelin/IAccessManager.sol';
    8
    import {IAccessManaged} from 'src/dependencies/openzeppelin/IAccessManaged.sol';
    9
    import {ContextUpgradeable} from './ContextUpgradeable.sol';
    10
    import {Initializable} from './Initializable.sol';
    11
    12
    /**
    13
     * @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be
    14
     * permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface,
    15
     * implementing a policy that allows certain callers to access certain functions.
    16
     *
    17
     * IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public`
    18
     * functions, and ideally only used in `external` functions. See {restricted}.
    19
     */
    20
    abstract contract AccessManagedUpgradeable is Initializable, ContextUpgradeable, IAccessManaged {
    21
      /// @custom:storage-location erc7201:openzeppelin.storage.AccessManaged
    22
      struct AccessManagedStorage {
    23
        address _authority;
    24
        bool _consumingSchedule;
    25
      }
    26
    27
      // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessManaged")) - 1)) & ~bytes32(uint256(0xff))
    28
      bytes32 private constant AccessManagedStorageLocation =
    29
        0xf3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00;
    30
    31
      function _getAccessManagedStorage() private pure returns (AccessManagedStorage storage $) {
    32
        assembly {
    33
          $.slot := AccessManagedStorageLocation
    34
        }
    35
      }
    36
    37
      /**
    38
       * @dev Initializes the contract connected to an initial authority.
    39
       */
    40 0
      function __AccessManaged_init(address initialAuthority) internal onlyInitializing {
    41 0
        __AccessManaged_init_unchained(initialAuthority);
    42
      }
    43
    44 0
      function __AccessManaged_init_unchained(address initialAuthority) internal onlyInitializing {
    45 0
        _setAuthority(initialAuthority);
    46
      }
    47
    48
      /**
    49
       * @dev Restricts access to a function as defined by the connected Authority for this contract and the
    50
       * caller and selector of the function that entered the contract.
    51
       *
    52
       * [IMPORTANT]
    53
       * ====
    54
       * In general, this modifier should only be used on `external` functions. It is okay to use it on `public`
    55
       * functions that are used as external entry points and are not called internally. Unless you know what you're
    56
       * doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security
    57
       * implications! This is because the permissions are determined by the function that entered the contract, i.e. the
    58
       * function at the bottom of the call stack, and not the function where the modifier is visible in the source code.
    59
       * ====
    60
       *
    61
       * [WARNING]
    62
       * ====
    63
       * Avoid adding this modifier to the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`]
    64
       * function or the https://docs.soliditylang.org/en/v0.8.20/contracts.html#fallback-function[`fallback()`]. These
    65
       * functions are the only execution paths where a function selector cannot be unambiguously determined from the calldata
    66
       * since the selector defaults to `0x00000000` in the `receive()` function and similarly in the `fallback()` function
    67
       * if no calldata is provided. (See {_checkCanCall}).
    68
       *
    69
       * The `receive()` function will always panic whereas the `fallback()` may panic depending on the calldata length.
    70
       * ====
    71
       */
    72
      modifier restricted() {
    73 18×
        _checkCanCall(_msgSender(), _msgData());
    74
        _;
    75
      }
    76
    77
      /// @inheritdoc IAccessManaged
    78
      function authority() public view virtual returns (address) {
    79
        AccessManagedStorage storage $ = _getAccessManagedStorage();
    80
        return $._authority;
    81
      }
    82
    83
      /// @inheritdoc IAccessManaged
    84
      function setAuthority(address newAuthority) public virtual {
    85
        address caller = _msgSender();
    86 0
        if (caller != authority()) {
    87 0
          revert AccessManagedUnauthorized(caller);
    88
        }
    89 0
        if (newAuthority.code.length == 0) {
    90 0
          revert AccessManagedInvalidAuthority(newAuthority);
    91
        }
    92
        _setAuthority(newAuthority);
    93
      }
    94
    95
      /// @inheritdoc IAccessManaged
    96 0
      function isConsumingScheduledOp() public view returns (bytes4) {
    97 0
        AccessManagedStorage storage $ = _getAccessManagedStorage();
    98 0
        return $._consumingSchedule ? this.isConsumingScheduledOp.selector : bytes4(0);
    99
      }
    100
    101
      /**
    102
       * @dev Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the
    103
       * permissions set by the current authority.
    104
       */
    105 0
      function _setAuthority(address newAuthority) internal virtual {
    106 0
        AccessManagedStorage storage $ = _getAccessManagedStorage();
    107 0
        $._authority = newAuthority;
    108 0
        emit AuthorityUpdated(newAuthority);
    109
      }
    110
    111
      /**
    112
       * @dev Reverts if the caller is not allowed to call the function identified by a selector. Panics if the calldata
    113
       * is less than 4 bytes long.
    114
       */
    115
      function _checkCanCall(address caller, bytes calldata data) internal virtual {
    116
        AccessManagedStorage storage $ = _getAccessManagedStorage();
    117 10×
        (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay(
    118
          authority(),
    119
          caller,
    120
          address(this),
    121 13×
          bytes4(data[0:4])
    122
        );
    123
        if (!immediate) {
    124
          if (delay > 0) {
    125 0
            $._consumingSchedule = true;
    126 0
            IAccessManager(authority()).consumeScheduledOp(caller, data);
    127 0
            $._consumingSchedule = false;
    128
          } else {
    129 0
            revert AccessManagedUnauthorized(caller);
    130
          }
    131
        }
    132
      }
    133
    }
    100% src/dependencies/openzeppelin-upgradeable/ContextUpgradeable.sol
    Lines covered: 3 / 3 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    import {Initializable} from './Initializable.sol';
    6
    7
    /**
    8
     * @dev Provides information about the current execution context, including the
    9
     * sender of the transaction and its data. While these are generally available
    10
     * via msg.sender and msg.data, they should not be accessed in such a direct
    11
     * manner, since when dealing with meta-transactions the account sending and
    12
     * paying for execution may not be the actual sender (as far as an application
    13
     * is concerned).
    14
     *
    15
     * This contract is only required for intermediate, library-like contracts.
    16
     */
    17
    abstract contract ContextUpgradeable is Initializable {
    18
        function __Context_init() internal onlyInitializing {
    19
        }
    20
    21
        function __Context_init_unchained() internal onlyInitializing {
    22
        }
    23
        function _msgSender() internal view virtual returns (address) {
    24
            return msg.sender;
    25
        }
    26
    27
        function _msgData() internal view virtual returns (bytes calldata) {
    28
            return msg.data;
    29
        }
    30
    31
        function _contextSuffixLength() internal view virtual returns (uint256) {
    32
            return 0;
    33
        }
    34
    }
    0% src/dependencies/openzeppelin-upgradeable/Initializable.sol
    Lines covered: 0 / 24 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
    8
     * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
    9
     * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
    10
     * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
    11
     *
    12
     * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
    13
     * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
    14
     * case an upgrade adds a module that needs to be initialized.
    15
     *
    16
     * For example:
    17
     *
    18
     * [.hljs-theme-light.nopadding]
    19
     * ```solidity
    20
     * contract MyToken is ERC20Upgradeable {
    21
     *     function initialize() initializer public {
    22
     *         __ERC20_init("MyToken", "MTK");
    23
     *     }
    24
     * }
    25
     *
    26
     * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
    27
     *     function initializeV2() reinitializer(2) public {
    28
     *         __ERC20Permit_init("MyToken");
    29
     *     }
    30
     * }
    31
     * ```
    32
     *
    33
     * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
    34
     * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
    35
     *
    36
     * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
    37
     * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
    38
     *
    39
     * [CAUTION]
    40
     * ====
    41
     * Avoid leaving a contract uninitialized.
    42
     *
    43
     * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
    44
     * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
    45
     * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
    46
     *
    47
     * [.hljs-theme-light.nopadding]
    48
     * ```
    49
     * /// @custom:oz-upgrades-unsafe-allow constructor
    50
     * constructor() {
    51
     *     _disableInitializers();
    52
     * }
    53
     * ```
    54
     * ====
    55
     */
    56
    abstract contract Initializable {
    57
      /**
    58
       * @dev Storage of the initializable contract.
    59
       *
    60
       * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
    61
       * when using with upgradeable contracts.
    62
       *
    63
       * @custom:storage-location erc7201:openzeppelin.storage.Initializable
    64
       */
    65
      struct InitializableStorage {
    66
        /**
    67
         * @dev Indicates that the contract has been initialized.
    68
         */
    69
        uint64 _initialized;
    70
        /**
    71
         * @dev Indicates that the contract is in the process of being initialized.
    72
         */
    73
        bool _initializing;
    74
      }
    75
    76
      // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    77
      bytes32 private constant INITIALIZABLE_STORAGE =
    78 0
        0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
    79
    80
      /**
    81
       * @dev The contract is already initialized.
    82
       */
    83
      error InvalidInitialization();
    84
    85
      /**
    86
       * @dev The contract is not initializing.
    87
       */
    88
      error NotInitializing();
    89
    90
      /**
    91
       * @dev Triggered when the contract has been initialized or reinitialized.
    92
       */
    93
      event Initialized(uint64 version);
    94
    95
      /**
    96
       * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
    97
       * `onlyInitializing` functions can be used to initialize parent contracts.
    98
       *
    99
       * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
    100
       * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
    101
       * production.
    102
       *
    103
       * Emits an {Initialized} event.
    104
       */
    105
      modifier initializer() {
    106
        // solhint-disable-next-line var-name-mixedcase
    107
        InitializableStorage storage $ = _getInitializableStorage();
    108
    109
        // Cache values to avoid duplicated sloads
    110
        bool isTopLevelCall = !$._initializing;
    111
        uint64 initialized = $._initialized;
    112
    113
        // Allowed calls:
    114
        // - initialSetup: the contract is not in the initializing state and no previous version was
    115
        //                 initialized
    116
        // - construction: the contract is initialized at version 1 (no reinitialization) and the
    117
        //                 current contract is just being deployed
    118
        bool initialSetup = initialized == 0 && isTopLevelCall;
    119
        bool construction = initialized == 1 && address(this).code.length == 0;
    120
    121
        if (!initialSetup && !construction) {
    122
          revert InvalidInitialization();
    123
        }
    124
        $._initialized = 1;
    125
        if (isTopLevelCall) {
    126
          $._initializing = true;
    127
        }
    128
        _;
    129
        if (isTopLevelCall) {
    130
          $._initializing = false;
    131
          emit Initialized(1);
    132
        }
    133
      }
    134
    135
      /**
    136
       * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
    137
       * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
    138
       * used to initialize parent contracts.
    139
       *
    140
       * A reinitializer may be used after the original initialization step. This is essential to configure modules that
    141
       * are added through upgrades and that require initialization.
    142
       *
    143
       * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
    144
       * cannot be nested. If one is invoked in the context of another, execution will revert.
    145
       *
    146
       * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
    147
       * a contract, executing them in the right order is up to the developer or operator.
    148
       *
    149
       * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
    150
       *
    151
       * Emits an {Initialized} event.
    152
       */
    153 0
      modifier reinitializer(uint64 version) {
    154
        // solhint-disable-next-line var-name-mixedcase
    155 0
        InitializableStorage storage $ = _getInitializableStorage();
    156
    157 0
        if ($._initializing || $._initialized >= version) {
    158 0
          revert InvalidInitialization();
    159
        }
    160 0
        $._initialized = version;
    161 0
        $._initializing = true;
    162
        _;
    163 0
        $._initializing = false;
    164 0
        emit Initialized(version);
    165
      }
    166
    167
      /**
    168
       * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
    169
       * {initializer} and {reinitializer} modifiers, directly or indirectly.
    170
       */
    171
      modifier onlyInitializing() {
    172 0
        _checkInitializing();
    173
        _;
    174
      }
    175
    176
      /**
    177
       * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
    178
       */
    179 0
      function _checkInitializing() internal view virtual {
    180 0
        if (!_isInitializing()) {
    181 0
          revert NotInitializing();
    182
        }
    183
      }
    184
    185
      /**
    186
       * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
    187
       * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
    188
       * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
    189
       * through proxies.
    190
       *
    191
       * Emits an {Initialized} event the first time it is successfully executed.
    192
       */
    193 0
      function _disableInitializers() internal virtual {
    194
        // solhint-disable-next-line var-name-mixedcase
    195
        InitializableStorage storage $ = _getInitializableStorage();
    196
    197 0
        if ($._initializing) {
    198 0
          revert InvalidInitialization();
    199
        }
    200 0
        if ($._initialized != type(uint64).max) {
    201 0
          $._initialized = type(uint64).max;
    202 0
          emit Initialized(type(uint64).max);
    203
        }
    204
      }
    205
    206
      /**
    207
       * @dev Returns the highest version that has been initialized. See {reinitializer}.
    208
       */
    209
      function _getInitializedVersion() internal view returns (uint64) {
    210
        return _getInitializableStorage()._initialized;
    211
      }
    212
    213
      /**
    214
       * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
    215
       */
    216 0
      function _isInitializing() internal view returns (bool) {
    217 0
        return _getInitializableStorage()._initializing;
    218
      }
    219
    220
      /**
    221
       * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
    222
       *
    223
       * NOTE: Consider following the ERC-7201 formula to derive storage locations.
    224
       */
    225 0
      function _initializableStorageSlot() internal pure virtual returns (bytes32) {
    226
        return INITIALIZABLE_STORAGE;
    227
      }
    228
    229
      /**
    230
       * @dev Returns a pointer to the storage namespace.
    231
       */
    232
      // solhint-disable-next-line var-name-mixedcase
    233 0
      function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
    234 0
        bytes32 slot = _initializableStorageSlot();
    235
        assembly {
    236
          $.slot := slot
    237
        }
    238
      }
    239
    }
    34% src/dependencies/solady/EIP712.sol
    Lines covered: 16 / 47 (34%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.4;
    3
    4
    /// @notice Contract for EIP-712 typed structured data hashing and signing.
    5
    /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol)
    6
    /// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol)
    7
    /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol)
    8
    ///
    9
    /// @dev Note, this implementation:
    10
    /// - Uses `address(this)` for the `verifyingContract` field.
    11
    /// - Does NOT use the optional EIP-712 salt.
    12
    /// - Does NOT use any EIP-712 extensions.
    13
    /// This is for simplicity and to save gas.
    14
    /// If you need to customize, please fork / modify accordingly.
    15
    abstract contract EIP712 {
    16
      /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    17
      /*                  CONSTANTS AND IMMUTABLES                  */
    18
      /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
    19
    20
      /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
    21
      bytes32 internal constant _DOMAIN_TYPEHASH =
    22
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
    23
    24
      uint256 private immutable _cachedThis;
    25
      uint256 private immutable _cachedChainId;
    26
      bytes32 private immutable _cachedNameHash;
    27
      bytes32 private immutable _cachedVersionHash;
    28
      bytes32 private immutable _cachedDomainSeparator;
    29
    30
      /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    31
      /*                        CONSTRUCTOR                         */
    32
      /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
    33
    34
      /// @dev Cache the hashes for cheaper runtime gas costs.
    35
      /// In the case of upgradeable contracts (i.e. proxies),
    36
      /// or if the chain id changes due to a hard fork,
    37
      /// the domain separator will be seamlessly calculated on-the-fly.
    38
      constructor() {
    39
        _cachedThis = uint256(uint160(address(this)));
    40
        _cachedChainId = block.chainid;
    41
    42
        string memory name;
    43
        string memory version;
    44
        if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion();
    45 11×
        bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name));
    46 10×
        bytes32 versionHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version));
    47
        _cachedNameHash = nameHash;
    48
        _cachedVersionHash = versionHash;
    49
    50
        bytes32 separator;
    51
        if (!_domainNameAndVersionMayChange()) {
    52
          /// @solidity memory-safe-assembly
    53
          assembly {
    54
            let m := mload(0x40) // Load the free memory pointer.
    55
            mstore(m, _DOMAIN_TYPEHASH)
    56
            mstore(add(m, 0x20), nameHash)
    57
            mstore(add(m, 0x40), versionHash)
    58
            mstore(add(m, 0x60), chainid())
    59
            mstore(add(m, 0x80), address())
    60
            separator := keccak256(m, 0xa0)
    61
          }
    62
        }
    63
        _cachedDomainSeparator = separator;
    64
      }
    65
    66
      /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    67
      /*                   FUNCTIONS TO OVERRIDE                    */
    68
      /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
    69
    70
      /// @dev Please override this function to return the domain name and version.
    71
      /// ```
    72
      ///     function _domainNameAndVersion()
    73
      ///         internal
    74
      ///         pure
    75
      ///         virtual
    76
      ///         returns (string memory name, string memory version)
    77
      ///     {
    78
      ///         name = "Solady";
    79
      ///         version = "1";
    80
      ///     }
    81
      /// ```
    82
      ///
    83
      /// Note: If the returned result may change after the contract has been deployed,
    84
      /// you must override `_domainNameAndVersionMayChange()` to return true.
    85
      function _domainNameAndVersion()
    86
        internal
    87
        view
    88
        virtual
    89
        returns (string memory name, string memory version);
    90
    91
      /// @dev Returns if `_domainNameAndVersion()` may change
    92
      /// after the contract has been deployed (i.e. after the constructor).
    93
      /// Default: false.
    94
      function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {}
    95
    96
      /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    97
      /*                     HASHING OPERATIONS                     */
    98
      /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
    99
    100
      /// @dev Returns the EIP-712 domain separator.
    101 0
      function _domainSeparator() internal view virtual returns (bytes32 separator) {
    102
        if (_domainNameAndVersionMayChange()) {
    103
          separator = _buildDomainSeparator();
    104
        } else {
    105 0
          separator = _cachedDomainSeparator;
    106 0
          if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator();
    107
        }
    108
      }
    109
    110
      /// @dev Returns the hash of the fully encoded EIP-712 message for this domain,
    111
      /// given `structHash`, as defined in
    112
      /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct.
    113
      ///
    114
      /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message:
    115
      /// ```
    116
      ///     bytes32 digest = _hashTypedData(keccak256(abi.encode(
    117
      ///         keccak256("Mail(address to,string contents)"),
    118
      ///         mailTo,
    119
      ///         keccak256(bytes(mailContents))
    120
      ///     )));
    121
      ///     address signer = ECDSA.recover(digest, signature);
    122
      /// ```
    123 0
      function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) {
    124
        // We will use `digest` to store the domain separator to save a bit of gas.
    125
        if (_domainNameAndVersionMayChange()) {
    126
          digest = _buildDomainSeparator();
    127
        } else {
    128 0
          digest = _cachedDomainSeparator;
    129 0
          if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator();
    130
        }
    131
        /// @solidity memory-safe-assembly
    132
        assembly {
    133
          // Compute the digest.
    134 0
          mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
    135 0
          mstore(0x1a, digest) // Store the domain separator.
    136 0
          mstore(0x3a, structHash) // Store the struct hash.
    137 0
          digest := keccak256(0x18, 0x42)
    138
          // Restore the part of the free memory slot that was overwritten.
    139 0
          mstore(0x3a, 0)
    140
        }
    141
      }
    142
    143
      /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    144
      /*                    EIP-5267 OPERATIONS                     */
    145
      /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
    146
    147
      /// @dev See: https://eips.ethereum.org/EIPS/eip-5267
    148 0
      function eip712Domain()
    149
        public
    150
        view
    151
        virtual
    152
        returns (
    153 0
          bytes1 fields,
    154 0
          string memory name,
    155
          string memory version,
    156
          uint256 chainId,
    157
          address verifyingContract,
    158
          bytes32 salt,
    159
          uint256[] memory extensions
    160
        )
    161
      {
    162
        fields = hex'0f'; // `0b01111`.
    163 0
        (name, version) = _domainNameAndVersion();
    164 0
        chainId = block.chainid;
    165 0
        verifyingContract = address(this);
    166
        salt = salt; // `bytes32(0)`.
    167
        extensions = extensions; // `new uint256[](0)`.
    168
      }
    169
    170
      /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    171
      /*                      PRIVATE HELPERS                       */
    172
      /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
    173
    174
      /// @dev Returns the EIP-712 domain separator.
    175 0
      function _buildDomainSeparator() private view returns (bytes32 separator) {
    176
        // We will use `separator` to store the name hash to save a bit of gas.
    177
        bytes32 versionHash;
    178
        if (_domainNameAndVersionMayChange()) {
    179
          (string memory name, string memory version) = _domainNameAndVersion();
    180
          separator = keccak256(bytes(name));
    181
          versionHash = keccak256(bytes(version));
    182
        } else {
    183 0
          separator = _cachedNameHash;
    184 0
          versionHash = _cachedVersionHash;
    185
        }
    186
        /// @solidity memory-safe-assembly
    187
        assembly {
    188 0
          let m := mload(0x40) // Load the free memory pointer.
    189 0
          mstore(m, _DOMAIN_TYPEHASH)
    190 0
          mstore(add(m, 0x20), separator) // Name hash.
    191 0
          mstore(add(m, 0x40), versionHash)
    192 0
          mstore(add(m, 0x60), chainid())
    193 0
          mstore(add(m, 0x80), address())
    194 0
          separator := keccak256(m, 0xa0)
    195
        }
    196
      }
    197
    198
      /// @dev Returns if the cached domain separator has been invalidated.
    199 0
      function _cachedDomainSeparatorInvalidated() private view returns (bool result) {
    200 0
        uint256 cachedChainId = _cachedChainId;
    201 0
        uint256 cachedThis = _cachedThis;
    202
        /// @solidity memory-safe-assembly
    203
        assembly {
    204 0
          result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis)))
    205
        }
    206
      }
    207
    }
    94% src/dependencies/solady/LibBit.sol
    Lines covered: 16 / 17 (94%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.4;
    3
    4
    // trimmed https://github.com/Vectorized/solady/blob/ba711c9fa6a2dc7b2b7707f7fe136b5133379c03/src/utils/LibBit.sol
    5
    6
    /// @notice Library for bit twiddling and boolean operations.
    7
    /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol)
    8
    /// @author Inspired by (https://graphics.stanford.edu/~seander/bithacks.html)
    9 0
    library LibBit {
    10
      /// @dev Returns the number of set bits in `x`.
    11
      function popCount(uint256 x) internal pure returns (uint256 c) {
    12
        /// @solidity memory-safe-assembly
    13
        assembly {
    14
          let max := not(0)
    15
          let isMax := eq(x, max)
    16
          x := sub(x, and(shr(1, x), div(max, 3)))
    17 11×
          x := add(and(x, div(max, 5)), and(shr(2, x), div(max, 5)))
    18
          x := and(add(x, shr(4, x)), div(max, 17))
    19
          c := or(shl(8, isMax), shr(248, mul(x, div(max, 255))))
    20
        }
    21
      }
    22
    23
      /// @dev Find last set.
    24
      /// Returns the index of the most significant bit of `x`,
    25
      /// counting from the least significant bit position.
    26
      /// If `x` is zero, returns 256.
    27
      function fls(uint256 x) internal pure returns (uint256 r) {
    28
        /// @solidity memory-safe-assembly
    29
        assembly {
    30
          r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, x)))
    31
          r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
    32
          r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
    33
          r := or(r, shl(4, lt(0xffff, shr(r, x))))
    34
          r := or(r, shl(3, lt(0xff, shr(r, x))))
    35
          // forgefmt: disable-next-item
    36
          r := or(
    37
            r,
    38
            byte(
    39
              and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
    40
              0x0706060506020504060203020504030106050205030304010505030400000000
    41
            )
    42
          )
    43
        }
    44
      }
    45
    }
    56% src/dependencies/weth/WETH9.sol
    Lines covered: 18 / 32 (56%)
    1
    // Copyright (C) 2015, 2016, 2017 Dapphub
    2
    3
    // This program is free software: you can redistribute it and/or modify
    4
    // it under the terms of the GNU General Public License as published by
    5
    // the Free Software Foundation, either version 3 of the License, or
    6
    // (at your option) any later version.
    7
    8
    // This program is distributed in the hope that it will be useful,
    9
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11
    // GNU General Public License for more details.
    12
    13
    // You should have received a copy of the GNU General Public License
    14
    // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15
    16
    pragma solidity ^0.8.20;
    17
    18 145×
    contract WETH9 {
    19 29×
      string public name = 'Wrapped Ether';
    20 24×
      string public symbol = 'WETH';
    21
      uint8 public decimals = 18;
    22
    23
      event Approval(address indexed src, address indexed guy, uint256 wad);
    24
      event Transfer(address indexed src, address indexed dst, uint256 wad);
    25
      event Deposit(address indexed dst, uint256 wad);
    26
      event Withdrawal(address indexed src, uint256 wad);
    27
    28 27×
      mapping(address => uint256) public balanceOf;
    29 0
      mapping(address => mapping(address => uint256)) public allowance;
    30
    31
      error InsufficientBalance();
    32
      error InsufficientAllowance();
    33
    34
      receive() external payable {
    35 0
        deposit();
    36
      }
    37
    38 0
      function deposit() public payable {
    39 0
        balanceOf[msg.sender] += msg.value;
    40 0
        emit Deposit(msg.sender, msg.value);
    41
      }
    42
    43 0
      function withdraw(uint256 wad) public {
    44 0
        require(balanceOf[msg.sender] >= wad);
    45 0
        balanceOf[msg.sender] -= wad;
    46 0
        payable(msg.sender).transfer(wad);
    47 0
        emit Withdrawal(msg.sender, wad);
    48
      }
    49
    50
      function totalSupply() public view returns (uint256) {
    51 0
        return address(this).balance;
    52
      }
    53
    54 20×
      function approve(address guy, uint256 wad) public returns (bool) {
    55 0
        allowance[msg.sender][guy] = wad;
    56 0
        emit Approval(msg.sender, guy, wad);
    57 0
        return true;
    58
      }
    59
    60 20×
      function transfer(address dst, uint256 wad) public returns (bool) {
    61
        return transferFrom(msg.sender, dst, wad);
    62
      }
    63
    64 32×
      function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
    65 47×
        require(balanceOf[src] >= wad, InsufficientBalance());
    66
    67 54×
        if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
    68 29×
          require(allowance[src][msg.sender] >= wad, InsufficientAllowance());
    69 38×
          allowance[src][msg.sender] -= wad;
    70
        }
    71
    72 54×
        balanceOf[src] -= wad;
    73 62×
        balanceOf[dst] += wad;
    74
    75 36×
        emit Transfer(src, dst, wad);
    76
    77
        return true;
    78
      }
    79
    }
    80
    81
    /*
    82
                        GNU GENERAL PUBLIC LICENSE
    83
                           Version 3, 29 June 2007
    84
    85
     Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
    86
     Everyone is permitted to copy and distribute verbatim copies
    87
     of this license document, but changing it is not allowed.
    88
    89
                                Preamble
    90
    91
      The GNU General Public License is a free, copyleft license for
    92
    software and other kinds of works.
    93
    94
      The licenses for most software and other practical works are designed
    95
    to take away your freedom to share and change the works.  By contrast,
    96
    the GNU General Public License is intended to guarantee your freedom to
    97
    share and change all versions of a program--to make sure it remains free
    98
    software for all its users.  We, the Free Software Foundation, use the
    99
    GNU General Public License for most of our software; it applies also to
    100
    any other work released this way by its authors.  You can apply it to
    101
    your programs, too.
    102
    103
      When we speak of free software, we are referring to freedom, not
    104
    price.  Our General Public Licenses are designed to make sure that you
    105
    have the freedom to distribute copies of free software (and charge for
    106
    them if you wish), that you receive source code or can get it if you
    107
    want it, that you can change the software or use pieces of it in new
    108
    free programs, and that you know you can do these things.
    109
    110
      To protect your rights, we need to prevent others from denying you
    111
    these rights or asking you to surrender the rights.  Therefore, you have
    112
    certain responsibilities if you distribute copies of the software, or if
    113
    you modify it: responsibilities to respect the freedom of others.
    114
    115
      For example, if you distribute copies of such a program, whether
    116
    gratis or for a fee, you must pass on to the recipients the same
    117
    freedoms that you received.  You must make sure that they, too, receive
    118
    or can get the source code.  And you must show them these terms so they
    119
    know their rights.
    120
    121
      Developers that use the GNU GPL protect your rights with two steps:
    122
    (1) assert copyright on the software, and (2) offer you this License
    123
    giving you legal permission to copy, distribute and/or modify it.
    124
    125
      For the developers' and authors' protection, the GPL clearly explains
    126
    that there is no warranty for this free software.  For both users' and
    127
    authors' sake, the GPL requires that modified versions be marked as
    128
    changed, so that their problems will not be attributed erroneously to
    129
    authors of previous versions.
    130
    131
      Some devices are designed to deny users access to install or run
    132
    modified versions of the software inside them, although the manufacturer
    133
    can do so.  This is fundamentally incompatible with the aim of
    134
    protecting users' freedom to change the software.  The systematic
    135
    pattern of such abuse occurs in the area of products for individuals to
    136
    use, which is precisely where it is most unacceptable.  Therefore, we
    137
    have designed this version of the GPL to prohibit the practice for those
    138
    products.  If such problems arise substantially in other domains, we
    139
    stand ready to extend this provision to those domains in future versions
    140
    of the GPL, as needed to protect the freedom of users.
    141
    142
      Finally, every program is threatened constantly by software patents.
    143
    States should not allow patents to restrict development and use of
    144
    software on general-purpose computers, but in those that do, we wish to
    145
    avoid the special danger that patents applied to a free program could
    146
    make it effectively proprietary.  To prevent this, the GPL assures that
    147
    patents cannot be used to render the program non-free.
    148
    149
      The precise terms and conditions for copying, distribution and
    150
    modification follow.
    151
    152
                           TERMS AND CONDITIONS
    153
    154
      0. Definitions.
    155
    156
      "This License" refers to version 3 of the GNU General Public License.
    157
    158
      "Copyright" also means copyright-like laws that apply to other kinds of
    159
    works, such as semiconductor masks.
    160
    161
      "The Program" refers to any copyrightable work licensed under this
    162
    License.  Each licensee is addressed as "you".  "Licensees" and
    163
    "recipients" may be individuals or organizations.
    164
    165
      To "modify" a work means to copy from or adapt all or part of the work
    166
    in a fashion requiring copyright permission, other than the making of an
    167
    exact copy.  The resulting work is called a "modified version" of the
    168
    earlier work or a work "based on" the earlier work.
    169
    170
      A "covered work" means either the unmodified Program or a work based
    171
    on the Program.
    172
    173
      To "propagate" a work means to do anything with it that, without
    174
    permission, would make you directly or secondarily liable for
    175
    infringement under applicable copyright law, except executing it on a
    176
    computer or modifying a private copy.  Propagation includes copying,
    177
    distribution (with or without modification), making available to the
    178
    public, and in some countries other activities as well.
    179
    180
      To "convey" a work means any kind of propagation that enables other
    181
    parties to make or receive copies.  Mere interaction with a user through
    182
    a computer network, with no transfer of a copy, is not conveying.
    183
    184
      An interactive user interface displays "Appropriate Legal Notices"
    185
    to the extent that it includes a convenient and prominently visible
    186
    feature that (1) displays an appropriate copyright notice, and (2)
    187
    tells the user that there is no warranty for the work (except to the
    188
    extent that warranties are provided), that licensees may convey the
    189
    work under this License, and how to view a copy of this License.  If
    190
    the interface presents a list of user commands or options, such as a
    191
    menu, a prominent item in the list meets this criterion.
    192
    193
      1. Source Code.
    194
    195
      The "source code" for a work means the preferred form of the work
    196
    for making modifications to it.  "Object code" means any non-source
    197
    form of a work.
    198
    199
      A "Standard Interface" means an interface that either is an official
    200
    standard defined by a recognized standards body, or, in the case of
    201
    interfaces specified for a particular programming language, one that
    202
    is widely used among developers working in that language.
    203
    204
      The "System Libraries" of an executable work include anything, other
    205
    than the work as a whole, that (a) is included in the normal form of
    206
    packaging a Major Component, but which is not part of that Major
    207
    Component, and (b) serves only to enable use of the work with that
    208
    Major Component, or to implement a Standard Interface for which an
    209
    implementation is available to the public in source code form.  A
    210
    "Major Component", in this context, means a major essential component
    211
    (kernel, window system, and so on) of the specific operating system
    212
    (if any) on which the executable work runs, or a compiler used to
    213
    produce the work, or an object code interpreter used to run it.
    214
    215
      The "Corresponding Source" for a work in object code form means all
    216
    the source code needed to generate, install, and (for an executable
    217
    work) run the object code and to modify the work, including scripts to
    218
    control those activities.  However, it does not include the work's
    219
    System Libraries, or general-purpose tools or generally available free
    220
    programs which are used unmodified in performing those activities but
    221
    which are not part of the work.  For example, Corresponding Source
    222
    includes interface definition files associated with source files for
    223
    the work, and the source code for shared libraries and dynamically
    224
    linked subprograms that the work is specifically designed to require,
    225
    such as by intimate data communication or control flow between those
    226
    subprograms and other parts of the work.
    227
    228
      The Corresponding Source need not include anything that users
    229
    can regenerate automatically from other parts of the Corresponding
    230
    Source.
    231
    232
      The Corresponding Source for a work in source code form is that
    233
    same work.
    234
    235
      2. Basic Permissions.
    236
    237
      All rights granted under this License are granted for the term of
    238
    copyright on the Program, and are irrevocable provided the stated
    239
    conditions are met.  This License explicitly affirms your unlimited
    240
    permission to run the unmodified Program.  The output from running a
    241
    covered work is covered by this License only if the output, given its
    242
    content, constitutes a covered work.  This License acknowledges your
    243
    rights of fair use or other equivalent, as provided by copyright law.
    244
    245
      You may make, run and propagate covered works that you do not
    246
    convey, without conditions so long as your license otherwise remains
    247
    in force.  You may convey covered works to others for the sole purpose
    248
    of having them make modifications exclusively for you, or provide you
    249
    with facilities for running those works, provided that you comply with
    250
    the terms of this License in conveying all material for which you do
    251
    not control copyright.  Those thus making or running the covered works
    252
    for you must do so exclusively on your behalf, under your direction
    253
    and control, on terms that prohibit them from making any copies of
    254
    your copyrighted material outside their relationship with you.
    255
    256
      Conveying under any other circumstances is permitted solely under
    257
    the conditions stated below.  Sublicensing is not allowed; section 10
    258
    makes it unnecessary.
    259
    260
      3. Protecting Users' Legal Rights From Anti-Circumvention Law.
    261
    262
      No covered work shall be deemed part of an effective technological
    263
    measure under any applicable law fulfilling obligations under article
    264
    11 of the WIPO copyright treaty adopted on 20 December 1996, or
    265
    similar laws prohibiting or restricting circumvention of such
    266
    measures.
    267
    268
      When you convey a covered work, you waive any legal power to forbid
    269
    circumvention of technological measures to the extent such circumvention
    270
    is effected by exercising rights under this License with respect to
    271
    the covered work, and you disclaim any intention to limit operation or
    272
    modification of the work as a means of enforcing, against the work's
    273
    users, your or third parties' legal rights to forbid circumvention of
    274
    technological measures.
    275
    276
      4. Conveying Verbatim Copies.
    277
    278
      You may convey verbatim copies of the Program's source code as you
    279
    receive it, in any medium, provided that you conspicuously and
    280
    appropriately publish on each copy an appropriate copyright notice;
    281
    keep intact all notices stating that this License and any
    282
    non-permissive terms added in accord with section 7 apply to the code;
    283
    keep intact all notices of the absence of any warranty; and give all
    284
    recipients a copy of this License along with the Program.
    285
    286
      You may charge any price or no price for each copy that you convey,
    287
    and you may offer support or warranty protection for a fee.
    288
    289
      5. Conveying Modified Source Versions.
    290
    291
      You may convey a work based on the Program, or the modifications to
    292
    produce it from the Program, in the form of source code under the
    293
    terms of section 4, provided that you also meet all of these conditions:
    294
    295
        a) The work must carry prominent notices stating that you modified
    296
        it, and giving a relevant date.
    297
    298
        b) The work must carry prominent notices stating that it is
    299
        released under this License and any conditions added under section
    300
        7.  This requirement modifies the requirement in section 4 to
    301
        "keep intact all notices".
    302
    303
        c) You must license the entire work, as a whole, under this
    304
        License to anyone who comes into possession of a copy.  This
    305
        License will therefore apply, along with any applicable section 7
    306
        additional terms, to the whole of the work, and all its parts,
    307
        regardless of how they are packaged.  This License gives no
    308
        permission to license the work in any other way, but it does not
    309
        invalidate such permission if you have separately received it.
    310
    311
        d) If the work has interactive user interfaces, each must display
    312
        Appropriate Legal Notices; however, if the Program has interactive
    313
        interfaces that do not display Appropriate Legal Notices, your
    314
        work need not make them do so.
    315
    316
      A compilation of a covered work with other separate and independent
    317
    works, which are not by their nature extensions of the covered work,
    318
    and which are not combined with it such as to form a larger program,
    319
    in or on a volume of a storage or distribution medium, is called an
    320
    "aggregate" if the compilation and its resulting copyright are not
    321
    used to limit the access or legal rights of the compilation's users
    322
    beyond what the individual works permit.  Inclusion of a covered work
    323
    in an aggregate does not cause this License to apply to the other
    324
    parts of the aggregate.
    325
    326
      6. Conveying Non-Source Forms.
    327
    328
      You may convey a covered work in object code form under the terms
    329
    of sections 4 and 5, provided that you also convey the
    330
    machine-readable Corresponding Source under the terms of this License,
    331
    in one of these ways:
    332
    333
        a) Convey the object code in, or embodied in, a physical product
    334
        (including a physical distribution medium), accompanied by the
    335
        Corresponding Source fixed on a durable physical medium
    336
        customarily used for software interchange.
    337
    338
        b) Convey the object code in, or embodied in, a physical product
    339
        (including a physical distribution medium), accompanied by a
    340
        written offer, valid for at least three years and valid for as
    341
        long as you offer spare parts or customer support for that product
    342
        model, to give anyone who possesses the object code either (1) a
    343
        copy of the Corresponding Source for all the software in the
    344
        product that is covered by this License, on a durable physical
    345
        medium customarily used for software interchange, for a price no
    346
        more than your reasonable cost of physically performing this
    347
        conveying of source, or (2) access to copy the
    348
        Corresponding Source from a network server at no charge.
    349
    350
        c) Convey individual copies of the object code with a copy of the
    351
        written offer to provide the Corresponding Source.  This
    352
        alternative is allowed only occasionally and noncommercially, and
    353
        only if you received the object code with such an offer, in accord
    354
        with subsection 6b.
    355
    356
        d) Convey the object code by offering access from a designated
    357
        place (gratis or for a charge), and offer equivalent access to the
    358
        Corresponding Source in the same way through the same place at no
    359
        further charge.  You need not require recipients to copy the
    360
        Corresponding Source along with the object code.  If the place to
    361
        copy the object code is a network server, the Corresponding Source
    362
        may be on a different server (operated by you or a third party)
    363
        that supports equivalent copying facilities, provided you maintain
    364
        clear directions next to the object code saying where to find the
    365
        Corresponding Source.  Regardless of what server hosts the
    366
        Corresponding Source, you remain obligated to ensure that it is
    367
        available for as long as needed to satisfy these requirements.
    368
    369
        e) Convey the object code using peer-to-peer transmission, provided
    370
        you inform other peers where the object code and Corresponding
    371
        Source of the work are being offered to the general public at no
    372
        charge under subsection 6d.
    373
    374
      A separable portion of the object code, whose source code is excluded
    375
    from the Corresponding Source as a System Library, need not be
    376
    included in conveying the object code work.
    377
    378
      A "User Product" is either (1) a "consumer product", which means any
    379
    tangible personal property which is normally used for personal, family,
    380
    or household purposes, or (2) anything designed or sold for incorporation
    381
    into a dwelling.  In determining whether a product is a consumer product,
    382
    doubtful cases shall be resolved in favor of coverage.  For a particular
    383
    product received by a particular user, "normally used" refers to a
    384
    typical or common use of that class of product, regardless of the status
    385
    of the particular user or of the way in which the particular user
    386
    actually uses, or expects or is expected to use, the product.  A product
    387
    is a consumer product regardless of whether the product has substantial
    388
    commercial, industrial or non-consumer uses, unless such uses represent
    389
    the only significant mode of use of the product.
    390
    391
      "Installation Information" for a User Product means any methods,
    392
    procedures, authorization keys, or other information required to install
    393
    and execute modified versions of a covered work in that User Product from
    394
    a modified version of its Corresponding Source.  The information must
    395
    suffice to ensure that the continued functioning of the modified object
    396
    code is in no case prevented or interfered with solely because
    397
    modification has been made.
    398
    399
      If you convey an object code work under this section in, or with, or
    400
    specifically for use in, a User Product, and the conveying occurs as
    401
    part of a transaction in which the right of possession and use of the
    402
    User Product is transferred to the recipient in perpetuity or for a
    403
    fixed term (regardless of how the transaction is characterized), the
    404
    Corresponding Source conveyed under this section must be accompanied
    405
    by the Installation Information.  But this requirement does not apply
    406
    if neither you nor any third party retains the ability to install
    407
    modified object code on the User Product (for example, the work has
    408
    been installed in ROM).
    409
    410
      The requirement to provide Installation Information does not include a
    411
    requirement to continue to provide support service, warranty, or updates
    412
    for a work that has been modified or installed by the recipient, or for
    413
    the User Product in which it has been modified or installed.  Access to a
    414
    network may be denied when the modification itself materially and
    415
    adversely affects the operation of the network or violates the rules and
    416
    protocols for communication across the network.
    417
    418
      Corresponding Source conveyed, and Installation Information provided,
    419
    in accord with this section must be in a format that is publicly
    420
    documented (and with an implementation available to the public in
    421
    source code form), and must require no special password or key for
    422
    unpacking, reading or copying.
    423
    424
      7. Additional Terms.
    425
    426
      "Additional permissions" are terms that supplement the terms of this
    427
    License by making exceptions from one or more of its conditions.
    428
    Additional permissions that are applicable to the entire Program shall
    429
    be treated as though they were included in this License, to the extent
    430
    that they are valid under applicable law.  If additional permissions
    431
    apply only to part of the Program, that part may be used separately
    432
    under those permissions, but the entire Program remains governed by
    433
    this License without regard to the additional permissions.
    434
    435
      When you convey a copy of a covered work, you may at your option
    436
    remove any additional permissions from that copy, or from any part of
    437
    it.  (Additional permissions may be written to require their own
    438
    removal in certain cases when you modify the work.)  You may place
    439
    additional permissions on material, added by you to a covered work,
    440
    for which you have or can give appropriate copyright permission.
    441
    442
      Notwithstanding any other provision of this License, for material you
    443
    add to a covered work, you may (if authorized by the copyright holders of
    444
    that material) supplement the terms of this License with terms:
    445
    446
        a) Disclaiming warranty or limiting liability differently from the
    447
        terms of sections 15 and 16 of this License; or
    448
    449
        b) Requiring preservation of specified reasonable legal notices or
    450
        author attributions in that material or in the Appropriate Legal
    451
        Notices displayed by works containing it; or
    452
    453
        c) Prohibiting misrepresentation of the origin of that material, or
    454
        requiring that modified versions of such material be marked in
    455
        reasonable ways as different from the original version; or
    456
    457
        d) Limiting the use for publicity purposes of names of licensors or
    458
        authors of the material; or
    459
    460
        e) Declining to grant rights under trademark law for use of some
    461
        trade names, trademarks, or service marks; or
    462
    463
        f) Requiring indemnification of licensors and authors of that
    464
        material by anyone who conveys the material (or modified versions of
    465
        it) with contractual assumptions of liability to the recipient, for
    466
        any liability that these contractual assumptions directly impose on
    467
        those licensors and authors.
    468
    469
      All other non-permissive additional terms are considered "further
    470
    restrictions" within the meaning of section 10.  If the Program as you
    471
    received it, or any part of it, contains a notice stating that it is
    472
    governed by this License along with a term that is a further
    473
    restriction, you may remove that term.  If a license document contains
    474
    a further restriction but permits relicensing or conveying under this
    475
    License, you may add to a covered work material governed by the terms
    476
    of that license document, provided that the further restriction does
    477
    not survive such relicensing or conveying.
    478
    479
      If you add terms to a covered work in accord with this section, you
    480
    must place, in the relevant source files, a statement of the
    481
    additional terms that apply to those files, or a notice indicating
    482
    where to find the applicable terms.
    483
    484
      Additional terms, permissive or non-permissive, may be stated in the
    485
    form of a separately written license, or stated as exceptions;
    486
    the above requirements apply either way.
    487
    488
      8. Termination.
    489
    490
      You may not propagate or modify a covered work except as expressly
    491
    provided under this License.  Any attempt otherwise to propagate or
    492
    modify it is void, and will automatically terminate your rights under
    493
    this License (including any patent licenses granted under the third
    494
    paragraph of section 11).
    495
    496
      However, if you cease all violation of this License, then your
    497
    license from a particular copyright holder is reinstated (a)
    498
    provisionally, unless and until the copyright holder explicitly and
    499
    finally terminates your license, and (b) permanently, if the copyright
    500
    holder fails to notify you of the violation by some reasonable means
    501
    prior to 60 days after the cessation.
    502
    503
      Moreover, your license from a particular copyright holder is
    504
    reinstated permanently if the copyright holder notifies you of the
    505
    violation by some reasonable means, this is the first time you have
    506
    received notice of violation of this License (for any work) from that
    507
    copyright holder, and you cure the violation prior to 30 days after
    508
    your receipt of the notice.
    509
    510
      Termination of your rights under this section does not terminate the
    511
    licenses of parties who have received copies or rights from you under
    512
    this License.  If your rights have been terminated and not permanently
    513
    reinstated, you do not qualify to receive new licenses for the same
    514
    material under section 10.
    515
    516
      9. Acceptance Not Required for Having Copies.
    517
    518
      You are not required to accept this License in order to receive or
    519
    run a copy of the Program.  Ancillary propagation of a covered work
    520
    occurring solely as a consequence of using peer-to-peer transmission
    521
    to receive a copy likewise does not require acceptance.  However,
    522
    nothing other than this License grants you permission to propagate or
    523
    modify any covered work.  These actions infringe copyright if you do
    524
    not accept this License.  Therefore, by modifying or propagating a
    525
    covered work, you indicate your acceptance of this License to do so.
    526
    527
      10. Automatic Licensing of Downstream Recipients.
    528
    529
      Each time you convey a covered work, the recipient automatically
    530
    receives a license from the original licensors, to run, modify and
    531
    propagate that work, subject to this License.  You are not responsible
    532
    for enforcing compliance by third parties with this License.
    533
    534
      An "entity transaction" is a transaction transferring control of an
    535
    organization, or substantially all assets of one, or subdividing an
    536
    organization, or merging organizations.  If propagation of a covered
    537
    work results from an entity transaction, each party to that
    538
    transaction who receives a copy of the work also receives whatever
    539
    licenses to the work the party's predecessor in interest had or could
    540
    give under the previous paragraph, plus a right to possession of the
    541
    Corresponding Source of the work from the predecessor in interest, if
    542
    the predecessor has it or can get it with reasonable efforts.
    543
    544
      You may not impose any further restrictions on the exercise of the
    545
    rights granted or affirmed under this License.  For example, you may
    546
    not impose a license fee, royalty, or other charge for exercise of
    547
    rights granted under this License, and you may not initiate litigation
    548
    (including a cross-claim or counterclaim in a lawsuit) alleging that
    549
    any patent claim is infringed by making, using, selling, offering for
    550
    sale, or importing the Program or any portion of it.
    551
    552
      11. Patents.
    553
    554
      A "contributor" is a copyright holder who authorizes use under this
    555
    License of the Program or a work on which the Program is based.  The
    556
    work thus licensed is called the contributor's "contributor version".
    557
    558
      A contributor's "essential patent claims" are all patent claims
    559
    owned or controlled by the contributor, whether already acquired or
    560
    hereafter acquired, that would be infringed by some manner, permitted
    561
    by this License, of making, using, or selling its contributor version,
    562
    but do not include claims that would be infringed only as a
    563
    consequence of further modification of the contributor version.  For
    564
    purposes of this definition, "control" includes the right to grant
    565
    patent sublicenses in a manner consistent with the requirements of
    566
    this License.
    567
    568
      Each contributor grants you a non-exclusive, worldwide, royalty-free
    569
    patent license under the contributor's essential patent claims, to
    570
    make, use, sell, offer for sale, import and otherwise run, modify and
    571
    propagate the contents of its contributor version.
    572
    573
      In the following three paragraphs, a "patent license" is any express
    574
    agreement or commitment, however denominated, not to enforce a patent
    575
    (such as an express permission to practice a patent or covenant not to
    576
    sue for patent infringement).  To "grant" such a patent license to a
    577
    party means to make such an agreement or commitment not to enforce a
    578
    patent against the party.
    579
    580
      If you convey a covered work, knowingly relying on a patent license,
    581
    and the Corresponding Source of the work is not available for anyone
    582
    to copy, free of charge and under the terms of this License, through a
    583
    publicly available network server or other readily accessible means,
    584
    then you must either (1) cause the Corresponding Source to be so
    585
    available, or (2) arrange to deprive yourself of the benefit of the
    586
    patent license for this particular work, or (3) arrange, in a manner
    587
    consistent with the requirements of this License, to extend the patent
    588
    license to downstream recipients.  "Knowingly relying" means you have
    589
    actual knowledge that, but for the patent license, your conveying the
    590
    covered work in a country, or your recipient's use of the covered work
    591
    in a country, would infringe one or more identifiable patents in that
    592
    country that you have reason to believe are valid.
    593
    594
      If, pursuant to or in connection with a single transaction or
    595
    arrangement, you convey, or propagate by procuring conveyance of, a
    596
    covered work, and grant a patent license to some of the parties
    597
    receiving the covered work authorizing them to use, propagate, modify
    598
    or convey a specific copy of the covered work, then the patent license
    599
    you grant is automatically extended to all recipients of the covered
    600
    work and works based on it.
    601
    602
      A patent license is "discriminatory" if it does not include within
    603
    the scope of its coverage, prohibits the exercise of, or is
    604
    conditioned on the non-exercise of one or more of the rights that are
    605
    specifically granted under this License.  You may not convey a covered
    606
    work if you are a party to an arrangement with a third party that is
    607
    in the business of distributing software, under which you make payment
    608
    to the third party based on the extent of your activity of conveying
    609
    the work, and under which the third party grants, to any of the
    610
    parties who would receive the covered work from you, a discriminatory
    611
    patent license (a) in connection with copies of the covered work
    612
    conveyed by you (or copies made from those copies), or (b) primarily
    613
    for and in connection with specific products or compilations that
    614
    contain the covered work, unless you entered into that arrangement,
    615
    or that patent license was granted, prior to 28 March 2007.
    616
    617
      Nothing in this License shall be construed as excluding or limiting
    618
    any implied license or other defenses to infringement that may
    619
    otherwise be available to you under applicable patent law.
    620
    621
      12. No Surrender of Others' Freedom.
    622
    623
      If conditions are imposed on you (whether by court order, agreement or
    624
    otherwise) that contradict the conditions of this License, they do not
    625
    excuse you from the conditions of this License.  If you cannot convey a
    626
    covered work so as to satisfy simultaneously your obligations under this
    627
    License and any other pertinent obligations, then as a consequence you may
    628
    not convey it at all.  For example, if you agree to terms that obligate you
    629
    to collect a royalty for further conveying from those to whom you convey
    630
    the Program, the only way you could satisfy both those terms and this
    631
    License would be to refrain entirely from conveying the Program.
    632
    633
      13. Use with the GNU Affero General Public License.
    634
    635
      Notwithstanding any other provision of this License, you have
    636
    permission to link or combine any covered work with a work licensed
    637
    under version 3 of the GNU Affero General Public License into a single
    638
    combined work, and to convey the resulting work.  The terms of this
    639
    License will continue to apply to the part which is the covered work,
    640
    but the special requirements of the GNU Affero General Public License,
    641
    section 13, concerning interaction through a network will apply to the
    642
    combination as such.
    643
    644
      14. Revised Versions of this License.
    645
    646
      The Free Software Foundation may publish revised and/or new versions of
    647
    the GNU General Public License from time to time.  Such new versions will
    648
    be similar in spirit to the present version, but may differ in detail to
    649
    address new problems or concerns.
    650
    651
      Each version is given a distinguishing version number.  If the
    652
    Program specifies that a certain numbered version of the GNU General
    653
    Public License "or any later version" applies to it, you have the
    654
    option of following the terms and conditions either of that numbered
    655
    version or of any later version published by the Free Software
    656
    Foundation.  If the Program does not specify a version number of the
    657
    GNU General Public License, you may choose any version ever published
    658
    by the Free Software Foundation.
    659
    660
      If the Program specifies that a proxy can decide which future
    661
    versions of the GNU General Public License can be used, that proxy's
    662
    public statement of acceptance of a version permanently authorizes you
    663
    to choose that version for the Program.
    664
    665
      Later license versions may give you additional or different
    666
    permissions.  However, no additional obligations are imposed on any
    667
    author or copyright holder as a result of your choosing to follow a
    668
    later version.
    669
    670
      15. Disclaimer of Warranty.
    671
    672
      THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
    673
    APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
    674
    HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
    675
    OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
    676
    THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    677
    PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
    678
    IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
    679
    ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
    680
    681
      16. Limitation of Liability.
    682
    683
      IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    684
    WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
    685
    THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
    686
    GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
    687
    USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
    688
    DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
    689
    PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
    690
    EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
    691
    SUCH DAMAGES.
    692
    693
      17. Interpretation of Sections 15 and 16.
    694
    695
      If the disclaimer of warranty and limitation of liability provided
    696
    above cannot be given local legal effect according to their terms,
    697
    reviewing courts shall apply local law that most closely approximates
    698
    an absolute waiver of all civil liability in connection with the
    699
    Program, unless a warranty or assumption of liability accompanies a
    700
    copy of the Program in return for a fee.
    701
    702
                         END OF TERMS AND CONDITIONS
    703
    704
                How to Apply These Terms to Your New Programs
    705
    706
      If you develop a new program, and you want it to be of the greatest
    707
    possible use to the public, the best way to achieve this is to make it
    708
    free software which everyone can redistribute and change under these terms.
    709
    710
      To do so, attach the following notices to the program.  It is safest
    711
    to attach them to the start of each source file to most effectively
    712
    state the exclusion of warranty; and each file should have at least
    713
    the "copyright" line and a pointer to where the full notice is found.
    714
    715
        <one line to give the program's name and a brief idea of what it does.>
    716
        Copyright (C) <year>  <name of author>
    717
    718
        This program is free software: you can redistribute it and/or modify
    719
        it under the terms of the GNU General Public License as published by
    720
        the Free Software Foundation, either version 3 of the License, or
    721
        (at your option) any later version.
    722
    723
        This program is distributed in the hope that it will be useful,
    724
        but WITHOUT ANY WARRANTY; without even the implied warranty of
    725
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    726
        GNU General Public License for more details.
    727
    728
        You should have received a copy of the GNU General Public License
    729
        along with this program.  If not, see <http://www.gnu.org/licenses/>.
    730
    731
    Also add information on how to contact you by electronic and paper mail.
    732
    733
      If the program does terminal interaction, make it output a short
    734
    notice like this when it starts in an interactive mode:
    735
    736
        <program>  Copyright (C) <year>  <name of author>
    737
        This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    738
        This is free software, and you are welcome to redistribute it
    739
        under certain conditions; type `show c' for details.
    740
    741
    The hypothetical commands `show w' and `show c' should show the appropriate
    742
    parts of the General Public License.  Of course, your program's commands
    743
    might be different; for a GUI interface, you would use an "about box".
    744
    745
      You should also get your employer (if you work as a programmer) or school,
    746
    if any, to sign a "copyright disclaimer" for the program, if necessary.
    747
    For more information on this, and how to apply and follow the GNU GPL, see
    748
    <http://www.gnu.org/licenses/>.
    749
    750
      The GNU General Public License does not permit incorporating your program
    751
    into proprietary programs.  If your program is a subroutine library, you
    752
    may consider it more useful to permit linking proprietary applications with
    753
    the library.  If this is what you want to do, use the GNU Lesser General
    754
    Public License instead of this License.  But first, please read
    755
    <http://www.gnu.org/philosophy/why-not-lgpl.html>.
    756
    757
    */
    72% src/hub/AssetInterestRateStrategy.sol
    Lines covered: 39 / 54 (72%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    6
    import {IAssetInterestRateStrategy, IBasicInterestRateStrategy} from 'src/hub/interfaces/IAssetInterestRateStrategy.sol';
    7
    8
    /// @title AssetInterestRateStrategy
    9
    /// @author Aave Labs
    10
    /// @notice Manages the kink-based interest rate strategy for an asset.
    11
    /// @dev Strategies are Hub-specific, due to the usage of asset identifier as index of the `_interestRateData` mapping.
    12 265×
    contract AssetInterestRateStrategy is IAssetInterestRateStrategy {
    13
      using WadRayMath for *;
    14
    15
      /// @inheritdoc IAssetInterestRateStrategy
    16
      uint256 public constant MAX_BORROW_RATE = 1000_00;
    17
    18
      /// @inheritdoc IAssetInterestRateStrategy
    19
      uint256 public constant MIN_OPTIMAL_RATIO = 1_00;
    20
    21
      /// @inheritdoc IAssetInterestRateStrategy
    22
      uint256 public constant MAX_OPTIMAL_RATIO = 99_00;
    23
    24
      /// @inheritdoc IAssetInterestRateStrategy
    25 0
      address public immutable HUB;
    26
    27
      /// @dev Map of asset identifiers to their interest rate data.
    28
      mapping(uint256 assetId => InterestRateData) internal _interestRateData;
    29
    30
      /// @dev Constructor.
    31
      /// @param hub_ The address of the associated Hub.
    32 27×
      constructor(address hub_) {
    33
        require(hub_ != address(0), InvalidAddress());
    34
        HUB = hub_;
    35
      }
    36
    37
      /// @notice Sets the interest rate parameters for a specified asset.
    38
      /// @param assetId The identifier of the asset.
    39
      /// @param data The encoded parameters containing BPS data used to configure the interest rate of the asset.
    40 18×
      function setInterestRateData(uint256 assetId, bytes calldata data) external {
    41
        require(HUB == msg.sender, OnlyHub());
    42 11×
        InterestRateData memory rateData = abi.decode(data, (InterestRateData));
    43 16×
        require(
    44 12×
          MIN_OPTIMAL_RATIO <= rateData.optimalUsageRatio &&
    45
            rateData.optimalUsageRatio <= MAX_OPTIMAL_RATIO,
    46
          InvalidOptimalUsageRatio()
    47
        );
    48 30×
        require(rateData.variableRateSlope1 <= rateData.variableRateSlope2, Slope2MustBeGteSlope1());
    49 16×
        require(
    50 28×
          rateData.baseVariableBorrowRate + rateData.variableRateSlope1 + rateData.variableRateSlope2 <=
    51
            MAX_BORROW_RATE,
    52
          InvalidMaxRate()
    53
        );
    54
    55 76×
        _interestRateData[assetId] = rateData;
    56
    57 13×
        emit UpdateRateData(
    58
          HUB,
    59
          assetId,
    60
          rateData.optimalUsageRatio,
    61
          rateData.baseVariableBorrowRate,
    62
          rateData.variableRateSlope1,
    63
          rateData.variableRateSlope2
    64
        );
    65
      }
    66
    67
      /// @inheritdoc IAssetInterestRateStrategy
    68 0
      function getInterestRateData(uint256 assetId) external view returns (InterestRateData memory) {
    69 0
        return _interestRateData[assetId];
    70
      }
    71
    72
      /// @inheritdoc IAssetInterestRateStrategy
    73 33×
      function getOptimalUsageRatio(uint256 assetId) external view returns (uint256) {
    74 0
        return _interestRateData[assetId].optimalUsageRatio;
    75
      }
    76
    77
      /// @inheritdoc IAssetInterestRateStrategy
    78 0
      function getBaseVariableBorrowRate(uint256 assetId) external view returns (uint256) {
    79 0
        return _interestRateData[assetId].baseVariableBorrowRate;
    80
      }
    81
    82
      /// @inheritdoc IAssetInterestRateStrategy
    83 0
      function getVariableRateSlope1(uint256 assetId) external view returns (uint256) {
    84 0
        return _interestRateData[assetId].variableRateSlope1;
    85
      }
    86
    87
      /// @inheritdoc IAssetInterestRateStrategy
    88 0
      function getVariableRateSlope2(uint256 assetId) external view returns (uint256) {
    89 0
        return _interestRateData[assetId].variableRateSlope2;
    90
      }
    91
    92
      /// @inheritdoc IAssetInterestRateStrategy
    93 0
      function getMaxVariableBorrowRate(uint256 assetId) external view returns (uint256) {
    94 0
        return
    95 0
          _interestRateData[assetId].baseVariableBorrowRate +
    96 0
          _interestRateData[assetId].variableRateSlope1 +
    97 0
          _interestRateData[assetId].variableRateSlope2;
    98
      }
    99
    100
      /// @inheritdoc IBasicInterestRateStrategy
    101 60×
      function calculateInterestRate(
    102
        uint256 assetId,
    103
        uint256 liquidity,
    104
        uint256 drawn,
    105
        uint256 /* deficit */,
    106
        uint256 swept
    107
      ) external view returns (uint256) {
    108 183×
        InterestRateData memory rateData = _interestRateData[assetId];
    109 12×
        require(rateData.optimalUsageRatio > 0, InterestRateDataNotSet(assetId));
    110
    111 39×
        uint256 currentVariableBorrowRateRay = rateData.baseVariableBorrowRate.bpsToRay();
    112 18×
        if (drawn == 0) {
    113 12×
          return currentVariableBorrowRateRay;
    114
        }
    115
    116 66×
        uint256 usageRatioRay = drawn.rayDivUp(liquidity + drawn + swept);
    117 39×
        uint256 optimalUsageRatioRay = rateData.optimalUsageRatio.bpsToRay();
    118
    119 27×
        if (usageRatioRay <= optimalUsageRatioRay) {
    120 84×
          currentVariableBorrowRateRay += rateData
    121
            .variableRateSlope1
    122
            .bpsToRay()
    123
            .rayMulUp(usageRatioRay)
    124
            .rayDivUp(optimalUsageRatioRay);
    125
        } else {
    126 24×
          currentVariableBorrowRateRay +=
    127 48×
            rateData.variableRateSlope1.bpsToRay() +
    128 36×
            rateData
    129
              .variableRateSlope2
    130
              .bpsToRay()
    131 18×
              .rayMulUp(usageRatioRay - optimalUsageRatioRay)
    132 15×
              .rayDivUp(WadRayMath.RAY - optimalUsageRatioRay);
    133
        }
    134
    135
        return currentVariableBorrowRateRay;
    136
      }
    137
    }
    60% src/hub/Hub.sol
    Lines covered: 288 / 478 (60%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol';
    6
    import {AccessManaged} from 'src/dependencies/openzeppelin/AccessManaged.sol';
    7
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    8
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    9
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    10
    import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
    11
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    12
    import {AssetLogic} from 'src/hub/libraries/AssetLogic.sol';
    13
    import {SharesMath} from 'src/hub/libraries/SharesMath.sol';
    14
    import {Premium} from 'src/hub/libraries/Premium.sol';
    15
    import {IBasicInterestRateStrategy} from 'src/hub/interfaces/IBasicInterestRateStrategy.sol';
    16
    import {IHubBase, IHub} from 'src/hub/interfaces/IHub.sol';
    17
    18
    /// @title Hub
    19
    /// @author Aave Labs
    20
    /// @notice A liquidity hub that manages assets and spokes.
    21 754×
    contract Hub is IHub, AccessManaged {
    22
      using EnumerableSet for EnumerableSet.AddressSet;
    23
      using SafeCast for *;
    24
      using SafeERC20 for IERC20;
    25
      using MathUtils for *;
    26
      using PercentageMath for *;
    27
      using WadRayMath for uint256;
    28
      using AssetLogic for Asset;
    29
      using SharesMath for uint256;
    30
    31
      /// @inheritdoc IHub
    32 0
      uint8 public constant MAX_ALLOWED_UNDERLYING_DECIMALS = 18;
    33
    34
      /// @inheritdoc IHub
    35 0
      uint8 public constant MIN_ALLOWED_UNDERLYING_DECIMALS = 6;
    36
    37
      /// @inheritdoc IHub
    38
      uint40 public constant MAX_ALLOWED_SPOKE_CAP = type(uint40).max;
    39
    40
      /// @inheritdoc IHub
    41 0
      uint24 public constant MAX_RISK_PREMIUM_THRESHOLD = type(uint24).max;
    42
    43
      /// @dev Number of assets listed in the Hub.
    44
      uint256 internal _assetCount;
    45
    46
      /// @dev Map of asset identifiers to Asset data.
    47
      mapping(uint256 assetId => Asset) internal _assets;
    48
    49
      /// @dev Map of asset identifiers and spoke addresses to Spoke data.
    50
      mapping(uint256 assetId => mapping(address spoke => SpokeData)) internal _spokes;
    51
    52
      /// @dev Map of asset identifiers to set of spoke addresses.
    53
      mapping(uint256 assetId => EnumerableSet.AddressSet) internal _assetToSpokes;
    54
    55
      /// @dev Set of underlying addresses listed as assets in the Hub.
    56
      EnumerableSet.AddressSet internal _underlyingAssets;
    57
    58
      /// @dev Constructor.
    59
      /// @dev The authority contract must implement the `AccessManaged` interface for access control.
    60
      /// @param authority_ The address of the authority contract which manages permissions.
    61 30×
      constructor(address authority_) AccessManaged(authority_) {
    62
        require(authority_ != address(0), InvalidAddress());
    63
      }
    64
    65
      /// @inheritdoc IHub
    66 0
      function addAsset(
    67
        address underlying,
    68
        uint8 decimals,
    69
        address feeReceiver,
    70
        address irStrategy,
    71
        bytes calldata irData
    72 0
      ) external restricted returns (uint256) {
    73 0
        require(
    74 0
          underlying != address(0) && feeReceiver != address(0) && irStrategy != address(0),
    75
          InvalidAddress()
    76
        );
    77 0
        require(
    78 0
          MIN_ALLOWED_UNDERLYING_DECIMALS <= decimals && decimals <= MAX_ALLOWED_UNDERLYING_DECIMALS,
    79
          InvalidAssetDecimals()
    80
        );
    81 0
        require(!_underlyingAssets.contains(underlying), UnderlyingAlreadyListed());
    82
    83 0
        uint256 assetId = _assetCount++;
    84 0
        IBasicInterestRateStrategy(irStrategy).setInterestRateData(assetId, irData);
    85 0
        uint256 drawnRate = IBasicInterestRateStrategy(irStrategy).calculateInterestRate({
    86
          assetId: assetId,
    87
          liquidity: 0,
    88
          drawn: 0,
    89
          deficit: 0,
    90
          swept: 0
    91
        });
    92
    93 0
        uint256 drawnIndex = WadRayMath.RAY;
    94 0
        uint256 lastUpdateTimestamp = block.timestamp;
    95 0
        _assets[assetId] = Asset({
    96
          liquidity: 0,
    97 0
          deficitRay: 0,
    98
          swept: 0,
    99
          addedShares: 0,
    100
          drawnShares: 0,
    101
          premiumShares: 0,
    102
          premiumOffsetRay: 0,
    103 0
          drawnIndex: drawnIndex.toUint120(),
    104 0
          underlying: underlying,
    105 0
          lastUpdateTimestamp: lastUpdateTimestamp.toUint40(),
    106
          decimals: decimals,
    107 0
          drawnRate: drawnRate.toUint96(),
    108 0
          irStrategy: irStrategy,
    109
          realizedFees: 0,
    110 0
          reinvestmentController: address(0),
    111 0
          feeReceiver: feeReceiver,
    112
          liquidityFee: 0
    113
        });
    114 0
        _underlyingAssets.add(underlying);
    115 0
        _addFeeReceiver(assetId, feeReceiver);
    116
    117 0
        emit AddAsset(assetId, underlying, decimals);
    118 0
        emit UpdateAssetConfig(
    119 0
          assetId,
    120 0
          AssetConfig({
    121
            feeReceiver: feeReceiver,
    122
            liquidityFee: 0,
    123
            irStrategy: irStrategy,
    124
            reinvestmentController: address(0)
    125
          })
    126
        );
    127 0
        emit UpdateAsset(assetId, drawnIndex, drawnRate, 0);
    128
    129 0
        return assetId;
    130
      }
    131
    132
      /// @inheritdoc IHub
    133 11×
      function updateAssetConfig(
    134
        uint256 assetId,
    135
        AssetConfig calldata config,
    136
        bytes calldata irData
    137 0
      ) external restricted {
    138
        require(assetId < _assetCount, AssetNotListed());
    139 11×
        Asset storage asset = _assets[assetId];
    140
        asset.accrue();
    141
    142 30×
        require(config.liquidityFee <= PercentageMath.PERCENTAGE_FACTOR, InvalidLiquidityFee());
    143 47×
        require(config.feeReceiver != address(0) && config.irStrategy != address(0), InvalidAddress());
    144
        require(
    145 26×
          config.reinvestmentController != address(0) || asset.swept == 0,
    146
          InvalidReinvestmentController()
    147
        );
    148
    149 20×
        if (config.irStrategy != asset.irStrategy) {
    150 26×
          asset.irStrategy = config.irStrategy;
    151 69×
          IBasicInterestRateStrategy(config.irStrategy).setInterestRateData(assetId, irData);
    152
        } else {
    153 17×
          require(irData.length == 0, InvalidInterestRateStrategy());
    154
        }
    155
    156 0
        address oldFeeReceiver = asset.feeReceiver;
    157 0
        if (oldFeeReceiver != config.feeReceiver) {
    158 0
          _mintFeeShares(asset, assetId);
    159 0
          IHub.SpokeConfig memory spokeConfig;
    160 0
          spokeConfig.active = _spokes[assetId][oldFeeReceiver].active;
    161 0
          spokeConfig.paused = _spokes[assetId][oldFeeReceiver].paused;
    162 0
          _updateSpokeConfig(assetId, oldFeeReceiver, spokeConfig);
    163 0
          asset.feeReceiver = config.feeReceiver;
    164 0
          _addFeeReceiver(assetId, config.feeReceiver);
    165
        }
    166
    167 0
        asset.liquidityFee = config.liquidityFee;
    168 0
        asset.reinvestmentController = config.reinvestmentController;
    169
    170 0
        asset.updateDrawnRate(assetId);
    171
    172 0
        emit UpdateAssetConfig(assetId, config);
    173
      }
    174
    175
      /// @inheritdoc IHub
    176 0
      function addSpoke(
    177
        uint256 assetId,
    178
        address spoke,
    179
        SpokeConfig calldata config
    180
      ) external restricted {
    181 0
        require(assetId < _assetCount, AssetNotListed());
    182 0
        require(spoke != address(0), InvalidAddress());
    183 0
        _addSpoke(assetId, spoke);
    184
        _updateSpokeConfig(assetId, spoke, config);
    185
      }
    186
    187
      /// @inheritdoc IHub
    188 23×
      function updateSpokeConfig(
    189
        uint256 assetId,
    190
        address spoke,
    191
        SpokeConfig calldata config
    192
      ) external restricted {
    193
        require(assetId < _assetCount, AssetNotListed());
    194 19×
        require(_assetToSpokes[assetId].contains(spoke), SpokeNotListed());
    195 19×
        _updateSpokeConfig(assetId, spoke, config);
    196
      }
    197
    198
      /// @inheritdoc IHub
    199 37×
      function setInterestRateData(uint256 assetId, bytes calldata irData) external restricted {
    200
        require(assetId < _assetCount, AssetNotListed());
    201 11×
        Asset storage asset = _assets[assetId];
    202
        asset.accrue();
    203 64×
        IBasicInterestRateStrategy(asset.irStrategy).setInterestRateData(assetId, irData);
    204 13×
        asset.updateDrawnRate(assetId);
    205
      }
    206
    207
      /// @inheritdoc IHub
    208 57×
      function mintFeeShares(uint256 assetId) external restricted returns (uint256) {
    209 11×
        Asset storage asset = _assets[assetId];
    210
        asset.accrue();
    211
        uint256 feeShares = _mintFeeShares(asset, assetId);
    212
        asset.updateDrawnRate(assetId);
    213
        return feeShares;
    214
      }
    215
    216
      /// @inheritdoc IHubBase
    217 16×
      function add(uint256 assetId, uint256 amount) external returns (uint256) {
    218 13×
        Asset storage asset = _assets[assetId];
    219 14×
        SpokeData storage spoke = _spokes[assetId][msg.sender];
    220
    221
        asset.accrue();
    222
        _validateAdd(asset, spoke, amount);
    223
    224 14×
        uint256 liquidity = asset.liquidity + amount;
    225 70×
        uint256 balance = IERC20(asset.underlying).balanceOf(address(this));
    226
        require(balance >= liquidity, InsufficientTransferred(liquidity.uncheckedSub(balance)));
    227 10×
        uint120 shares = asset.toAddedSharesDown(amount).toUint120();
    228 20×
        require(shares > 0, InvalidShares());
    229 36×
        asset.addedShares += shares;
    230 40×
        spoke.addedShares += shares;
    231 16×
        asset.liquidity = liquidity.toUint120();
    232
    233
        asset.updateDrawnRate(assetId);
    234
    235 30×
        emit Add(assetId, msg.sender, shares, amount);
    236
    237
        return shares;
    238
      }
    239
    240
      /// @inheritdoc IHubBase
    241 24×
      function remove(uint256 assetId, uint256 amount, address to) external returns (uint256) {
    242 26×
        Asset storage asset = _assets[assetId];
    243 28×
        SpokeData storage spoke = _spokes[assetId][msg.sender];
    244
    245
        asset.accrue();
    246 14×
        _validateRemove(spoke, amount, to);
    247
    248
        uint256 liquidity = asset.liquidity;
    249 22×
        require(amount <= liquidity, InsufficientLiquidity(liquidity));
    250
    251 28×
        uint120 shares = asset.toAddedSharesUp(amount).toUint120();
    252 62×
        asset.addedShares -= shares;
    253 80×
        spoke.addedShares -= shares;
    254 46×
        asset.liquidity = liquidity.uncheckedSub(amount).toUint120();
    255
    256 10×
        asset.updateDrawnRate(assetId);
    257
    258 24×
        IERC20(asset.underlying).safeTransfer(to, amount);
    259
    260 28×
        emit Remove(assetId, msg.sender, shares, amount);
    261
    262
        return shares;
    263
      }
    264
    265
      /// @inheritdoc IHubBase
    266 12×
      function draw(uint256 assetId, uint256 amount, address to) external returns (uint256) {
    267 13×
        Asset storage asset = _assets[assetId];
    268 14×
        SpokeData storage spoke = _spokes[assetId][msg.sender];
    269
    270
        asset.accrue();
    271
        _validateDraw(asset, spoke, amount, to);
    272
    273
        uint256 liquidity = asset.liquidity;
    274 15×
        require(amount <= liquidity, InsufficientLiquidity(liquidity));
    275
    276 14×
        uint120 drawnShares = asset.toDrawnSharesUp(amount).toUint120();
    277 31×
        asset.drawnShares += drawnShares;
    278 40×
        spoke.drawnShares += drawnShares;
    279 23×
        asset.liquidity = liquidity.uncheckedSub(amount).toUint120();
    280
    281
        asset.updateDrawnRate(assetId);
    282
    283 12×
        IERC20(asset.underlying).safeTransfer(to, amount);
    284
    285 14×
        emit Draw(assetId, msg.sender, drawnShares, amount);
    286
    287
        return drawnShares;
    288
      }
    289
    290
      /// @inheritdoc IHubBase
    291 24×
      function restore(
    292
        uint256 assetId,
    293
        uint256 drawnAmount,
    294
        PremiumDelta calldata premiumDelta
    295
      ) external returns (uint256) {
    296 26×
        Asset storage asset = _assets[assetId];
    297 28×
        SpokeData storage spoke = _spokes[assetId][msg.sender];
    298
    299
        asset.accrue();
    300 22×
        _validateRestore(asset, spoke, drawnAmount, premiumDelta.restoredPremiumRay);
    301
    302 28×
        uint120 drawnShares = asset.toDrawnSharesDown(drawnAmount).toUint120();
    303 62×
        asset.drawnShares -= drawnShares;
    304 80×
        spoke.drawnShares -= drawnShares;
    305 14×
        _applyPremiumDelta(asset, spoke, premiumDelta);
    306
    307 26×
        uint256 premiumAmount = premiumDelta.restoredPremiumRay.fromRayUp();
    308 40×
        uint256 liquidity = asset.liquidity + drawnAmount + premiumAmount;
    309 140×
        uint256 balance = IERC20(asset.underlying).balanceOf(address(this));
    310 16×
        require(balance >= liquidity, InsufficientTransferred(liquidity.uncheckedSub(balance)));
    311 32×
        asset.liquidity = liquidity.toUint120();
    312
    313 10×
        asset.updateDrawnRate(assetId);
    314
    315 52×
        emit Restore(assetId, msg.sender, drawnShares, premiumDelta, drawnAmount, premiumAmount);
    316
    317
        return drawnShares;
    318
      }
    319
    320
      /// @inheritdoc IHubBase
    321 13×
      function reportDeficit(
    322
        uint256 assetId,
    323
        uint256 drawnAmount,
    324
        PremiumDelta calldata premiumDelta
    325
      ) external returns (uint256) {
    326 13×
        Asset storage asset = _assets[assetId];
    327 14×
        SpokeData storage spoke = _spokes[assetId][msg.sender];
    328
    329
        asset.accrue();
    330 11×
        _validateReportDeficit(asset, spoke, drawnAmount, premiumDelta.restoredPremiumRay);
    331
    332 14×
        uint120 drawnShares = asset.toDrawnSharesDown(drawnAmount).toUint120();
    333 31×
        asset.drawnShares -= drawnShares;
    334 40×
        spoke.drawnShares -= drawnShares;
    335
        _applyPremiumDelta(asset, spoke, premiumDelta);
    336
    337 17×
        uint256 deficitAmountRay = uint256(drawnShares) *
    338
          asset.drawnIndex +
    339
          premiumDelta.restoredPremiumRay;
    340 38×
        asset.deficitRay += deficitAmountRay.toUint200();
    341 38×
        spoke.deficitRay += deficitAmountRay.toUint200();
    342
    343 11×
        asset.updateDrawnRate(assetId);
    344
    345 24×
        emit ReportDeficit(assetId, msg.sender, drawnShares, premiumDelta, deficitAmountRay);
    346
    347
        return drawnShares;
    348
      }
    349
    350
      /// @inheritdoc IHub
    351 0
      function eliminateDeficit(
    352
        uint256 assetId,
    353
        uint256 amount,
    354
        address spoke
    355 0
      ) external returns (uint256) {
    356 0
        Asset storage asset = _assets[assetId];
    357 0
        SpokeData storage callerSpoke = _spokes[assetId][msg.sender];
    358 0
        SpokeData storage coveredSpoke = _spokes[assetId][spoke];
    359
    360 0
        asset.accrue();
    361 0
        _validateEliminateDeficit(callerSpoke, amount);
    362
    363 0
        uint256 deficitRay = coveredSpoke.deficitRay;
    364 0
        uint256 deficitAmountRay = (amount < deficitRay.fromRayUp()) ? amount.toRay() : deficitRay;
    365
    366 0
        uint120 shares = asset.toAddedSharesUp(deficitAmountRay.fromRayUp()).toUint120();
    367 0
        asset.addedShares -= shares;
    368 0
        callerSpoke.addedShares -= shares;
    369 0
        asset.deficitRay = asset.deficitRay.uncheckedSub(deficitAmountRay).toUint200();
    370 0
        coveredSpoke.deficitRay = deficitRay.uncheckedSub(deficitAmountRay).toUint200();
    371
    372 0
        asset.updateDrawnRate(assetId);
    373
    374 0
        emit EliminateDeficit(assetId, msg.sender, spoke, shares, deficitAmountRay);
    375
    376 0
        return shares;
    377
      }
    378
    379
      /// @inheritdoc IHubBase
    380 11×
      function refreshPremium(uint256 assetId, PremiumDelta calldata premiumDelta) external {
    381 14×
        Asset storage asset = _assets[assetId];
    382 15×
        SpokeData storage spoke = _spokes[assetId][msg.sender];
    383
    384
        asset.accrue();
    385 24×
        require(spoke.active, SpokeNotActive());
    386
        // no premium change allowed
    387
        require(premiumDelta.restoredPremiumRay == 0, InvalidPremiumChange());
    388
        _applyPremiumDelta(asset, spoke, premiumDelta);
    389
        asset.updateDrawnRate(assetId);
    390
    391 12×
        emit RefreshPremium(assetId, msg.sender, premiumDelta);
    392
      }
    393
    394
      /// @inheritdoc IHubBase
    395 18×
      function payFeeShares(uint256 assetId, uint256 shares) external {
    396 14×
        Asset storage asset = _assets[assetId];
    397
        address feeReceiver = _assets[assetId].feeReceiver;
    398 14×
        SpokeData storage receiver = _spokes[assetId][feeReceiver];
    399
        SpokeData storage sender = _spokes[assetId][msg.sender];
    400
    401
        asset.accrue();
    402
        _validatePayFeeShares(sender, shares);
    403
        _transferShares(sender, receiver, shares);
    404
        asset.updateDrawnRate(assetId);
    405
    406 19×
        emit TransferShares(assetId, msg.sender, feeReceiver, shares);
    407
      }
    408
    409
      /// @inheritdoc IHub
    410 0
      function transferShares(uint256 assetId, uint256 shares, address toSpoke) external {
    411 0
        Asset storage asset = _assets[assetId];
    412 0
        SpokeData storage sender = _spokes[assetId][msg.sender];
    413 0
        SpokeData storage receiver = _spokes[assetId][toSpoke];
    414
    415 0
        asset.accrue();
    416 0
        _validateTransferShares(asset, sender, receiver, shares);
    417 0
        _transferShares(sender, receiver, shares);
    418 0
        asset.updateDrawnRate(assetId);
    419
    420 0
        emit TransferShares(assetId, msg.sender, toSpoke, shares);
    421
      }
    422
    423
      /// @inheritdoc IHub
    424 16×
      function sweep(uint256 assetId, uint256 amount) external {
    425 0
        require(assetId < _assetCount, AssetNotListed());
    426 0
        Asset storage asset = _assets[assetId];
    427
    428 0
        asset.accrue();
    429 0
        _validateSweep(asset, msg.sender, amount);
    430
    431 0
        uint256 liquidity = asset.liquidity;
    432
        require(amount <= liquidity, InsufficientLiquidity(liquidity));
    433
    434
        asset.liquidity = liquidity.uncheckedSub(amount).toUint120();
    435 0
        asset.swept += amount.toUint120();
    436 0
        asset.updateDrawnRate(assetId);
    437
    438 0
        IERC20(asset.underlying).safeTransfer(msg.sender, amount);
    439
    440 16×
        emit Sweep(assetId, msg.sender, amount);
    441
      }
    442
    443
      /// @inheritdoc IHub
    444 0
      function reclaim(uint256 assetId, uint256 amount) external {
    445 0
        require(assetId < _assetCount, AssetNotListed());
    446 0
        Asset storage asset = _assets[assetId];
    447
    448 0
        asset.accrue();
    449 0
        _validateReclaim(asset, msg.sender, amount);
    450
    451 0
        asset.liquidity += amount.toUint120();
    452 0
        asset.swept -= amount.toUint120();
    453 0
        asset.updateDrawnRate(assetId);
    454
    455 0
        IERC20(asset.underlying).safeTransferFrom(msg.sender, address(this), amount);
    456
    457 0
        emit Reclaim(assetId, msg.sender, amount);
    458
      }
    459
    460
      /// @inheritdoc IHub
    461
      function isUnderlyingListed(address underlying) external view returns (bool) {
    462
        return _underlyingAssets.contains(underlying);
    463
      }
    464
    465
      /// @inheritdoc IHub
    466
      function getAssetCount() external view returns (uint256) {
    467
        return _assetCount;
    468
      }
    469
    470
      /// @inheritdoc IHubBase
    471 12×
      function previewAddByAssets(uint256 assetId, uint256 assets) external view returns (uint256) {
    472 14×
        return _assets[assetId].toAddedSharesDown(assets);
    473
      }
    474
    475
      /// @inheritdoc IHubBase
    476 0
      function previewAddByShares(uint256 assetId, uint256 shares) external view returns (uint256) {
    477 0
        return _assets[assetId].toAddedAssetsUp(shares);
    478
      }
    479
    480
      /// @inheritdoc IHubBase
    481 12×
      function previewRemoveByAssets(uint256 assetId, uint256 assets) external view returns (uint256) {
    482 14×
        return _assets[assetId].toAddedSharesUp(assets);
    483
      }
    484
    485
      /// @inheritdoc IHubBase
    486 24×
      function previewRemoveByShares(uint256 assetId, uint256 shares) external view returns (uint256) {
    487 28×
        return _assets[assetId].toAddedAssetsDown(shares);
    488
      }
    489
    490
      /// @inheritdoc IHubBase
    491 0
      function previewDrawByAssets(uint256 assetId, uint256 assets) external view returns (uint256) {
    492 0
        return _assets[assetId].toDrawnSharesUp(assets);
    493
      }
    494
    495
      /// @inheritdoc IHubBase
    496 0
      function previewDrawByShares(uint256 assetId, uint256 shares) external view returns (uint256) {
    497 0
        return _assets[assetId].toDrawnAssetsDown(shares);
    498
      }
    499
    500
      /// @inheritdoc IHubBase
    501 0
      function previewRestoreByAssets(uint256 assetId, uint256 assets) external view returns (uint256) {
    502 0
        return _assets[assetId].toDrawnSharesDown(assets);
    503
      }
    504
    505
      /// @inheritdoc IHubBase
    506 0
      function previewRestoreByShares(uint256 assetId, uint256 shares) external view returns (uint256) {
    507 0
        return _assets[assetId].toDrawnAssetsUp(shares);
    508
      }
    509
    510
      /// @inheritdoc IHubBase
    511 0
      function getAssetUnderlyingAndDecimals(uint256 assetId) external view returns (address, uint8) {
    512 0
        Asset storage asset = _assets[assetId];
    513 0
        return (asset.underlying, asset.decimals);
    514
      }
    515
    516
      /// @inheritdoc IHubBase
    517 12×
      function getAssetDrawnIndex(uint256 assetId) external view returns (uint256) {
    518 13×
        return _assets[assetId].getDrawnIndex();
    519
      }
    520
    521
      /// @inheritdoc IHubBase
    522 24×
      function getAddedAssets(uint256 assetId) external view returns (uint256) {
    523 26×
        return _assets[assetId].totalAddedAssets();
    524
      }
    525
    526
      /// @inheritdoc IHubBase
    527 20×
      function getAddedShares(uint256 assetId) external view returns (uint256) {
    528 28×
        return _assets[assetId].addedShares;
    529
      }
    530
    531
      /// @inheritdoc IHubBase
    532 0
      function getAssetOwed(uint256 assetId) external view returns (uint256, uint256) {
    533 0
        Asset storage asset = _assets[assetId];
    534 0
        uint256 drawnIndex = asset.getDrawnIndex();
    535 0
        return (asset.drawn(drawnIndex), asset.premium(drawnIndex));
    536
      }
    537
    538
      /// @inheritdoc IHubBase
    539 18×
      function getAssetTotalOwed(uint256 assetId) external view returns (uint256) {
    540 10×
        Asset storage asset = _assets[assetId];
    541 15×
        return asset.totalOwed(asset.getDrawnIndex());
    542
      }
    543
    544
      /// @inheritdoc IHubBase
    545 0
      function getAssetPremiumRay(uint256 assetId) external view returns (uint256) {
    546 0
        Asset storage asset = _assets[assetId];
    547
        return
    548
          Premium.calculatePremiumRay({
    549 0
            premiumShares: asset.premiumShares,
    550 0
            premiumOffsetRay: asset.premiumOffsetRay,
    551
            drawnIndex: asset.getDrawnIndex()
    552
          });
    553
      }
    554
    555
      /// @inheritdoc IHubBase
    556 10×
      function getAssetDrawnShares(uint256 assetId) external view returns (uint256) {
    557 14×
        return _assets[assetId].drawnShares;
    558
      }
    559
    560
      /// @inheritdoc IHubBase
    561 0
      function getAssetPremiumData(uint256 assetId) external view returns (uint256, int256) {
    562 0
        Asset storage asset = _assets[assetId];
    563 0
        return (asset.premiumShares, asset.premiumOffsetRay);
    564
      }
    565
    566
      /// @inheritdoc IHubBase
    567 0
      function getAssetLiquidity(uint256 assetId) external view returns (uint256) {
    568 0
        return _assets[assetId].liquidity;
    569
      }
    570
    571
      /// @inheritdoc IHubBase
    572 0
      function getAssetDeficitRay(uint256 assetId) external view returns (uint256) {
    573 0
        return _assets[assetId].deficitRay;
    574
      }
    575
    576
      /// @inheritdoc IHub
    577 0
      function getAsset(uint256 assetId) external view returns (Asset memory) {
    578 0
        return _assets[assetId];
    579
      }
    580
    581
      /// @inheritdoc IHub
    582 0
      function getAssetConfig(uint256 assetId) external view returns (AssetConfig memory) {
    583 0
        Asset storage asset = _assets[assetId];
    584
        return
    585 0
          AssetConfig({
    586 0
            feeReceiver: asset.feeReceiver,
    587 0
            liquidityFee: asset.liquidityFee,
    588 0
            irStrategy: asset.irStrategy,
    589 0
            reinvestmentController: asset.reinvestmentController
    590
          });
    591
      }
    592
    593
      /// @inheritdoc IHub
    594 12×
      function getAssetAccruedFees(uint256 assetId) external view returns (uint256) {
    595 10×
        Asset storage asset = _assets[assetId];
    596 20×
        return asset.realizedFees + asset.getUnrealizedFees(asset.getDrawnIndex());
    597
      }
    598
    599
      /// @inheritdoc IHub
    600 0
      function getAssetSwept(uint256 assetId) external view returns (uint256) {
    601 0
        return _assets[assetId].swept;
    602
      }
    603
    604
      /// @inheritdoc IHub
    605 0
      function getAssetDrawnRate(uint256 assetId) external view returns (uint256) {
    606 0
        return _assets[assetId].drawnRate;
    607
      }
    608
    609
      /// @inheritdoc IHub
    610 24×
      function getSpokeCount(uint256 assetId) external view returns (uint256) {
    611 26×
        return _assetToSpokes[assetId].length();
    612
      }
    613
    614
      /// @inheritdoc IHubBase
    615 12×
      function getSpokeAddedAssets(uint256 assetId, address spoke) external view returns (uint256) {
    616 38×
        return _assets[assetId].toAddedAssetsDown(_spokes[assetId][spoke].addedShares);
    617
      }
    618
    619
      /// @inheritdoc IHubBase
    620 10×
      function getSpokeAddedShares(uint256 assetId, address spoke) external view returns (uint256) {
    621 28×
        return _spokes[assetId][spoke].addedShares;
    622
      }
    623
    624
      /// @inheritdoc IHubBase
    625 14×
      function getSpokeOwed(uint256 assetId, address spoke) external view returns (uint256, uint256) {
    626 0
        Asset storage asset = _assets[assetId];
    627 0
        SpokeData storage spokeData = _spokes[assetId][spoke];
    628 0
        return (_getSpokeDrawn(asset, spokeData), _getSpokePremium(asset, spokeData));
    629
      }
    630
    631
      /// @inheritdoc IHubBase
    632 0
      function getSpokeTotalOwed(uint256 assetId, address spoke) external view returns (uint256) {
    633 0
        Asset storage asset = _assets[assetId];
    634 0
        SpokeData storage spokeData = _spokes[assetId][spoke];
    635 0
        return _getSpokeDrawn(asset, spokeData) + _getSpokePremium(asset, spokeData);
    636
      }
    637
    638
      /// @inheritdoc IHubBase
    639
      function getSpokePremiumRay(uint256 assetId, address spoke) external view returns (uint256) {
    640 0
        Asset storage asset = _assets[assetId];
    641 0
        SpokeData storage spokeData = _spokes[assetId][spoke];
    642
        return _getSpokePremiumRay(asset, spokeData);
    643
      }
    644
    645
      /// @inheritdoc IHubBase
    646 10×
      function getSpokeDrawnShares(uint256 assetId, address spoke) external view returns (uint256) {
    647 26×
        return _spokes[assetId][spoke].drawnShares;
    648
      }
    649
    650
      /// @inheritdoc IHubBase
    651 0
      function getSpokePremiumData(
    652
        uint256 assetId,
    653
        address spoke
    654 0
      ) external view returns (uint256, int256) {
    655 0
        SpokeData storage spokeData = _spokes[assetId][spoke];
    656 0
        return (spokeData.premiumShares, spokeData.premiumOffsetRay);
    657
      }
    658
    659
      /// @inheritdoc IHubBase
    660 0
      function getSpokeDeficitRay(uint256 assetId, address spoke) external view returns (uint256) {
    661 0
        return _spokes[assetId][spoke].deficitRay;
    662
      }
    663
    664
      /// @inheritdoc IHub
    665 0
      function isSpokeListed(uint256 assetId, address spoke) external view returns (bool) {
    666 0
        return _assetToSpokes[assetId].contains(spoke);
    667
      }
    668
    669
      /// @inheritdoc IHub
    670 40×
      function getSpokeAddress(uint256 assetId, uint256 index) external view returns (address) {
    671 28×
        return _assetToSpokes[assetId].at(index);
    672
      }
    673
    674
      /// @inheritdoc IHub
    675 0
      function getSpoke(uint256 assetId, address spoke) external view returns (SpokeData memory) {
    676 0
        return _spokes[assetId][spoke];
    677
      }
    678
    679
      /// @inheritdoc IHub
    680 0
      function getSpokeConfig(
    681
        uint256 assetId,
    682
        address spoke
    683 0
      ) external view returns (SpokeConfig memory) {
    684 0
        SpokeData storage spokeData = _spokes[assetId][spoke];
    685
        return
    686 0
          SpokeConfig({
    687 0
            addCap: spokeData.addCap,
    688 0
            drawCap: spokeData.drawCap,
    689 0
            riskPremiumThreshold: spokeData.riskPremiumThreshold,
    690 0
            active: spokeData.active,
    691 0
            paused: spokeData.paused
    692
          });
    693
      }
    694
    695
      /// @notice Adds a new spoke to an asset with default feeReceiver configuration (maximum add cap, zero draw cap).
    696 0
      function _addFeeReceiver(uint256 assetId, address feeReceiver) internal {
    697 0
        _addSpoke(assetId, feeReceiver);
    698 0
        _updateSpokeConfig(
    699 0
          assetId,
    700 0
          feeReceiver,
    701 0
          SpokeConfig({
    702
            addCap: MAX_ALLOWED_SPOKE_CAP,
    703
            drawCap: 0,
    704
            riskPremiumThreshold: 0,
    705 0
            active: true,
    706
            paused: false
    707
          })
    708
        );
    709
      }
    710
    711
      /// @notice Adds a spoke to an asset.
    712
      /// @dev Reverts with `SpokeAlreadyListed` if spoke is already listed for the given asset.
    713 0
      function _addSpoke(uint256 assetId, address spoke) internal {
    714 0
        require(_assetToSpokes[assetId].add(spoke), SpokeAlreadyListed());
    715 0
        emit AddSpoke(assetId, spoke);
    716
      }
    717
    718
      function _updateSpokeConfig(uint256 assetId, address spoke, SpokeConfig memory config) internal {
    719 30×
        SpokeData storage spokeData = _spokes[assetId][spoke];
    720 14×
        spokeData.addCap = config.addCap;
    721 21×
        spokeData.drawCap = config.drawCap;
    722 12×
        spokeData.riskPremiumThreshold = config.riskPremiumThreshold;
    723 18×
        spokeData.active = config.active;
    724 19×
        spokeData.paused = config.paused;
    725
        emit UpdateSpokeConfig(assetId, spoke, config);
    726
      }
    727
    728
      /// @dev Receiver `addCap` is validated in `_validateTransferShares`.
    729
      function _transferShares(
    730
        SpokeData storage sender,
    731
        SpokeData storage receiver,
    732
        uint256 shares
    733
      ) internal {
    734 38×
        sender.addedShares -= shares.toUint120();
    735 38×
        receiver.addedShares += shares.toUint120();
    736
      }
    737
    738
      /// @dev Applies premium deltas on asset & spoke premium owed.
    739
      /// @dev Checks premium owed decreases by exactly `restoredPremiumRay`.
    740
      /// @dev Checks updated risk premium is within allowed threshold.
    741
      /// @dev Uses last stored index; asset accrual should have already occurred.
    742
      function _applyPremiumDelta(
    743
        Asset storage asset,
    744
        SpokeData storage spoke,
    745
        PremiumDelta calldata premiumDelta
    746
      ) internal {
    747 20×
        uint256 drawnIndex = asset.drawnIndex;
    748
    749
        // asset premium change
    750 80×
        (asset.premiumShares, asset.premiumOffsetRay) = _validateApplyPremiumDelta(
    751
          drawnIndex,
    752 22×
          asset.premiumShares,
    753 12×
          asset.premiumOffsetRay,
    754
          premiumDelta
    755
        );
    756
    757
        // spoke premium change
    758 68×
        (spoke.premiumShares, spoke.premiumOffsetRay) = _validateApplyPremiumDelta(
    759
          drawnIndex,
    760 18×
          spoke.premiumShares,
    761
          spoke.premiumOffsetRay,
    762
          premiumDelta
    763
        );
    764
    765 22×
        uint24 riskPremiumThreshold = spoke.riskPremiumThreshold;
    766 30×
        require(
    767 12×
          riskPremiumThreshold == MAX_RISK_PREMIUM_THRESHOLD ||
    768 36×
            spoke.premiumShares <= spoke.drawnShares.percentMulUp(riskPremiumThreshold),
    769
          InvalidPremiumChange()
    770
        );
    771
      }
    772
    773
      function _mintFeeShares(Asset storage asset, uint256 assetId) internal returns (uint256) {
    774
        uint256 fees = asset.realizedFees;
    775
        uint120 shares = asset.toAddedSharesDown(fees).toUint120();
    776
        if (shares == 0) {
    777
          return 0;
    778
        }
    779
    780
        address feeReceiver = asset.feeReceiver;
    781 21×
        SpokeData storage feeReceiverSpoke = _spokes[assetId][feeReceiver];
    782 24×
        require(feeReceiverSpoke.active, SpokeNotActive());
    783
    784 36×
        asset.addedShares += shares;
    785 46×
        feeReceiverSpoke.addedShares += shares;
    786
        asset.realizedFees = 0;
    787 25×
        emit MintFeeShares(assetId, feeReceiver, shares, fees);
    788
    789
        return shares;
    790
      }
    791
    792
      /// @dev Returns the spoke's drawn amount for a specified asset.
    793
      function _getSpokeDrawn(
    794
        Asset storage asset,
    795
        SpokeData storage spoke
    796
      ) internal view returns (uint256) {
    797 18×
        return asset.toDrawnAssetsUp(spoke.drawnShares);
    798
      }
    799
    800
      /// @dev Returns the spoke's premium amount for a specified asset.
    801
      function _getSpokePremium(
    802
        Asset storage asset,
    803
        SpokeData storage spoke
    804
      ) internal view returns (uint256) {
    805
        return _getSpokePremiumRay(asset, spoke).fromRayUp();
    806
      }
    807
    808
      /// @dev Returns the spoke's premium amount with full precision for a specified asset.
    809
      function _getSpokePremiumRay(
    810
        Asset storage asset,
    811
        SpokeData storage spoke
    812
      ) internal view returns (uint256) {
    813
        return
    814
          Premium.calculatePremiumRay({
    815 14×
            premiumShares: spoke.premiumShares,
    816 12×
            premiumOffsetRay: spoke.premiumOffsetRay,
    817
            drawnIndex: asset.getDrawnIndex()
    818
          });
    819
      }
    820
    821
      /// @dev Spoke with maximum cap have unlimited add capacity.
    822
      function _validateAdd(
    823
        Asset storage asset,
    824
        SpokeData storage spoke,
    825
        uint256 amount
    826
      ) internal view {
    827 19×
        require(amount > 0, InvalidAmount());
    828 24×
        require(spoke.active, SpokeNotActive());
    829 25×
        require(!spoke.paused, SpokePaused());
    830 11×
        uint256 addCap = spoke.addCap;
    831 11×
        require(
    832
          addCap == MAX_ALLOWED_SPOKE_CAP ||
    833 15×
            addCap * MathUtils.uncheckedExp(10, asset.decimals) >=
    834 20×
            asset.toAddedAssetsUp(spoke.addedShares) + amount,
    835
          AddCapExceeded(addCap)
    836
        );
    837
      }
    838
    839
      function _validateRemove(SpokeData storage spoke, uint256 amount, address to) internal view {
    840 14×
        require(to != address(this), InvalidAddress());
    841 25×
        require(amount > 0, InvalidAmount());
    842 48×
        require(spoke.active, SpokeNotActive());
    843 48×
        require(!spoke.paused, SpokePaused());
    844
      }
    845
    846
      /// @dev Spoke with maximum cap have unlimited draw capacity.
    847
      function _validateDraw(
    848
        Asset storage asset,
    849
        SpokeData storage spoke,
    850
        uint256 amount,
    851
        address to
    852
      ) internal view {
    853
        require(to != address(this), InvalidAddress());
    854 19×
        require(amount > 0, InvalidAmount());
    855 24×
        require(spoke.active, SpokeNotActive());
    856 25×
        require(!spoke.paused, SpokePaused());
    857 10×
        uint256 drawCap = spoke.drawCap;
    858 18×
        uint256 owed = _getSpokeDrawn(asset, spoke) + _getSpokePremium(asset, spoke);
    859 11×
        require(
    860
          drawCap == MAX_ALLOWED_SPOKE_CAP ||
    861 15×
            drawCap * MathUtils.uncheckedExp(10, asset.decimals) >=
    862 22×
            owed + amount + uint256(spoke.deficitRay).fromRayUp(),
    863
          DrawCapExceeded(drawCap)
    864
        );
    865
      }
    866
    867 12×
      function _validateRestore(
    868
        Asset storage asset,
    869
        SpokeData storage spoke,
    870
        uint256 drawnAmount,
    871
        uint256 premiumAmountRay
    872
      ) internal view {
    873 41×
        require(drawnAmount > 0 || premiumAmountRay > 0, InvalidAmount());
    874 48×
        require(spoke.active, SpokeNotActive());
    875 50×
        require(!spoke.paused, SpokePaused());
    876 18×
        uint256 drawn = _getSpokeDrawn(asset, spoke);
    877 16×
        uint256 premiumRay = _getSpokePremiumRay(asset, spoke);
    878 16×
        require(drawnAmount <= drawn, SurplusDrawnRestored(drawn));
    879 18×
        require(premiumAmountRay <= premiumRay, SurplusPremiumRayRestored(premiumRay));
    880
      }
    881
    882
      function _validateReportDeficit(
    883
        Asset storage asset,
    884
        SpokeData storage spoke,
    885
        uint256 drawnAmount,
    886
        uint256 premiumAmountRay
    887
      ) internal view {
    888 11×
        require(spoke.active, SpokeNotActive());
    889 12×
        require(!spoke.paused, SpokePaused());
    890 10×
        require(drawnAmount > 0 || premiumAmountRay > 0, InvalidAmount());
    891
        uint256 drawn = _getSpokeDrawn(asset, spoke);
    892
        uint256 premiumRay = _getSpokePremiumRay(asset, spoke);
    893
        require(drawnAmount <= drawn, SurplusDrawnDeficitReported(drawn));
    894
        require(premiumAmountRay <= premiumRay, SurplusPremiumRayDeficitReported(premiumRay));
    895
      }
    896
    897 0
      function _validateEliminateDeficit(SpokeData storage spoke, uint256 amount) internal view {
    898 0
        require(spoke.active, SpokeNotActive());
    899 0
        require(amount > 0, InvalidAmount());
    900
      }
    901
    902
      function _validatePayFeeShares(SpokeData storage senderSpoke, uint256 feeShares) internal view {
    903 24×
        require(senderSpoke.active, SpokeNotActive());
    904 25×
        require(!senderSpoke.paused, SpokePaused());
    905
        require(feeShares > 0, InvalidShares());
    906
      }
    907
    908 0
      function _validateTransferShares(
    909
        Asset storage asset,
    910
        SpokeData storage sender,
    911
        SpokeData storage receiver,
    912
        uint256 shares
    913 0
      ) internal view {
    914 0
        require(sender.active && receiver.active, SpokeNotActive());
    915 0
        require(!sender.paused && !receiver.paused, SpokePaused());
    916 0
        require(shares > 0, InvalidShares());
    917 0
        uint256 addCap = receiver.addCap;
    918 0
        require(
    919 0
          addCap == MAX_ALLOWED_SPOKE_CAP ||
    920 0
            addCap * MathUtils.uncheckedExp(10, asset.decimals) >=
    921 0
            asset.toAddedAssetsUp(receiver.addedShares + shares),
    922 0
          AddCapExceeded(addCap)
    923
        );
    924
      }
    925
    926 0
      function _validateSweep(Asset storage asset, address caller, uint256 amount) internal view {
    927
        // sufficient check to disallow when controller unset
    928 0
        require(caller == asset.reinvestmentController, OnlyReinvestmentController());
    929 0
        require(amount > 0, InvalidAmount());
    930
      }
    931
    932
      function _validateReclaim(Asset storage asset, address caller, uint256 amount) internal view {
    933
        // sufficient check to disallow when controller unset
    934
        require(caller == asset.reinvestmentController, OnlyReinvestmentController());
    935
        require(amount > 0, InvalidAmount());
    936
      }
    937
    938
      /// @dev Validates applied premium delta for given premium data and returns updated premium data.
    939 18×
      function _validateApplyPremiumDelta(
    940
        uint256 drawnIndex,
    941
        uint256 premiumShares,
    942
        int256 premiumOffsetRay,
    943
        PremiumDelta calldata premiumDelta
    944
      ) internal pure returns (uint120, int200) {
    945 12×
        uint256 premiumRayBefore = Premium.calculatePremiumRay({
    946
          premiumShares: premiumShares,
    947
          premiumOffsetRay: premiumOffsetRay,
    948
          drawnIndex: drawnIndex
    949
        });
    950
    951 18×
        uint256 newPremiumShares = premiumShares.add(premiumDelta.sharesDelta);
    952 24×
        int256 newPremiumOffsetRay = premiumOffsetRay + premiumDelta.offsetRayDelta;
    953
    954 14×
        uint256 premiumRayAfter = Premium.calculatePremiumRay({
    955
          premiumShares: newPremiumShares,
    956
          premiumOffsetRay: newPremiumOffsetRay,
    957
          drawnIndex: drawnIndex
    958
        });
    959
    960
        require(
    961 20×
          premiumRayAfter + premiumDelta.restoredPremiumRay == premiumRayBefore,
    962
          InvalidPremiumChange()
    963
        );
    964 36×
        return (newPremiumShares.toUint120(), newPremiumOffsetRay.toInt200());
    965
      }
    966
    }
    0% src/hub/HubConfigurator.sol
    Lines covered: 0 / 140 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {IERC20Metadata} from 'src/dependencies/openzeppelin/IERC20Metadata.sol';
    6
    import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
    7
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    8
    import {IHub} from 'src/hub/interfaces/IHub.sol';
    9
    import {IHubConfigurator} from 'src/hub/interfaces/IHubConfigurator.sol';
    10
    11
    /// @title HubConfigurator
    12
    /// @author Aave Labs
    13
    /// @notice Handles administrative functions on the Hub.
    14
    /// @dev Must be granted permission by the Hub.
    15 0
    contract HubConfigurator is Ownable2Step, IHubConfigurator {
    16
      using SafeCast for uint256;
    17
    18
      /// @dev Constructor.
    19
      /// @param owner_ The address of the owner.
    20 0
      constructor(address owner_) Ownable(owner_) {}
    21
    22
      /// @inheritdoc IHubConfigurator
    23 0
      function addAsset(
    24
        address hub,
    25
        address underlying,
    26
        address feeReceiver,
    27
        uint256 liquidityFee,
    28
        address irStrategy,
    29
        bytes calldata irData
    30 0
      ) external onlyOwner returns (uint256) {
    31 0
        IHub targetHub = IHub(hub);
    32 0
        uint256 assetId = targetHub.addAsset(
    33 0
          underlying,
    34 0
          IERC20Metadata(underlying).decimals(),
    35 0
          feeReceiver,
    36 0
          irStrategy,
    37 0
          irData
    38
        );
    39 0
        _updateLiquidityFee(targetHub, assetId, liquidityFee);
    40 0
        return assetId;
    41
      }
    42
    43
      /// @inheritdoc IHubConfigurator
    44 0
      function addAsset(
    45
        address hub,
    46
        address underlying,
    47
        uint8 decimals,
    48
        address feeReceiver,
    49
        uint256 liquidityFee,
    50
        address irStrategy,
    51
        bytes calldata irData
    52 0
      ) external onlyOwner returns (uint256) {
    53 0
        IHub targetHub = IHub(hub);
    54 0
        uint256 assetId = targetHub.addAsset(underlying, decimals, feeReceiver, irStrategy, irData);
    55 0
        _updateLiquidityFee(targetHub, assetId, liquidityFee);
    56 0
        return assetId;
    57
      }
    58
    59
      /// @inheritdoc IHubConfigurator
    60 0
      function updateLiquidityFee(
    61
        address hub,
    62
        uint256 assetId,
    63
        uint256 liquidityFee
    64
      ) external onlyOwner {
    65 0
        _updateLiquidityFee(IHub(hub), assetId, liquidityFee);
    66
      }
    67
    68
      /// @inheritdoc IHubConfigurator
    69 0
      function updateFeeReceiver(address hub, uint256 assetId, address feeReceiver) external onlyOwner {
    70 0
        IHub targetHub = IHub(hub);
    71 0
        IHub.AssetConfig memory config = targetHub.getAssetConfig(assetId);
    72 0
        config.feeReceiver = feeReceiver;
    73 0
        targetHub.updateAssetConfig(assetId, config, new bytes(0));
    74
      }
    75
    76
      /// @inheritdoc IHubConfigurator
    77 0
      function updateFeeConfig(
    78
        address hub,
    79
        uint256 assetId,
    80
        uint256 liquidityFee,
    81
        address feeReceiver
    82 0
      ) external onlyOwner {
    83 0
        IHub targetHub = IHub(hub);
    84 0
        IHub.AssetConfig memory config = targetHub.getAssetConfig(assetId);
    85 0
        config.liquidityFee = liquidityFee.toUint16();
    86 0
        config.feeReceiver = feeReceiver;
    87 0
        targetHub.updateAssetConfig(assetId, config, new bytes(0));
    88
      }
    89
    90
      /// @inheritdoc IHubConfigurator
    91 0
      function updateInterestRateStrategy(
    92
        address hub,
    93
        uint256 assetId,
    94
        address irStrategy,
    95
        bytes calldata irData
    96 0
      ) external onlyOwner {
    97 0
        IHub targetHub = IHub(hub);
    98 0
        IHub.AssetConfig memory config = targetHub.getAssetConfig(assetId);
    99 0
        config.irStrategy = irStrategy;
    100 0
        targetHub.updateAssetConfig(assetId, config, irData);
    101
      }
    102
    103
      /// @inheritdoc IHubConfigurator
    104 0
      function updateReinvestmentController(
    105
        address hub,
    106
        uint256 assetId,
    107
        address reinvestmentController
    108 0
      ) external onlyOwner {
    109 0
        IHub targetHub = IHub(hub);
    110 0
        IHub.AssetConfig memory config = targetHub.getAssetConfig(assetId);
    111 0
        config.reinvestmentController = reinvestmentController;
    112 0
        targetHub.updateAssetConfig(assetId, config, new bytes(0));
    113
      }
    114
    115
      /// @inheritdoc IHubConfigurator
    116 0
      function freezeAsset(address hub, uint256 assetId) external onlyOwner {
    117 0
        IHub targetHub = IHub(hub);
    118 0
        uint256 spokesCount = targetHub.getSpokeCount(assetId);
    119
    120 0
        for (uint256 i = 0; i < spokesCount; ++i) {
    121 0
          address spoke = targetHub.getSpokeAddress(assetId, i);
    122 0
          IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    123 0
          config.addCap = 0;
    124 0
          config.drawCap = 0;
    125 0
          targetHub.updateSpokeConfig(assetId, spoke, config);
    126
        }
    127
      }
    128
    129
      /// @inheritdoc IHubConfigurator
    130 0
      function deactivateAsset(address hub, uint256 assetId) external onlyOwner {
    131 0
        IHub targetHub = IHub(hub);
    132 0
        uint256 spokesCount = targetHub.getSpokeCount(assetId);
    133 0
        for (uint256 i = 0; i < spokesCount; ++i) {
    134 0
          address spoke = targetHub.getSpokeAddress(assetId, i);
    135 0
          IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    136 0
          config.active = false;
    137 0
          targetHub.updateSpokeConfig(assetId, spoke, config);
    138
        }
    139
      }
    140
    141
      /// @inheritdoc IHubConfigurator
    142 0
      function pauseAsset(address hub, uint256 assetId) external onlyOwner {
    143 0
        IHub targetHub = IHub(hub);
    144 0
        uint256 spokesCount = targetHub.getSpokeCount(assetId);
    145 0
        for (uint256 i = 0; i < spokesCount; ++i) {
    146 0
          address spoke = targetHub.getSpokeAddress(assetId, i);
    147 0
          IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    148 0
          config.paused = true;
    149 0
          targetHub.updateSpokeConfig(assetId, spoke, config);
    150
        }
    151
      }
    152
    153
      /// @inheritdoc IHubConfigurator
    154 0
      function addSpoke(
    155
        address hub,
    156
        address spoke,
    157
        uint256 assetId,
    158
        IHub.SpokeConfig calldata config
    159
      ) external onlyOwner {
    160 0
        IHub(hub).addSpoke(assetId, spoke, config);
    161
      }
    162
    163
      /// @inheritdoc IHubConfigurator
    164 0
      function addSpokeToAssets(
    165
        address hub,
    166
        address spoke,
    167
        uint256[] calldata assetIds,
    168
        IHub.SpokeConfig[] calldata configs
    169
      ) external onlyOwner {
    170 0
        uint256 assetCount = assetIds.length;
    171 0
        require(assetCount == configs.length, MismatchedConfigs());
    172 0
        for (uint256 i = 0; i < assetCount; ++i) {
    173 0
          IHub(hub).addSpoke(assetIds[i], spoke, configs[i]);
    174
        }
    175
      }
    176
    177
      /// @inheritdoc IHubConfigurator
    178 0
      function updateSpokeActive(
    179
        address hub,
    180
        uint256 assetId,
    181
        address spoke,
    182
        bool active
    183
      ) external onlyOwner {
    184 0
        IHub targetHub = IHub(hub);
    185 0
        IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    186 0
        config.active = active;
    187 0
        targetHub.updateSpokeConfig(assetId, spoke, config);
    188
      }
    189
    190
      /// @inheritdoc IHubConfigurator
    191 0
      function updateSpokePaused(
    192
        address hub,
    193
        uint256 assetId,
    194
        address spoke,
    195
        bool paused
    196
      ) external onlyOwner {
    197 0
        IHub targetHub = IHub(hub);
    198 0
        IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    199 0
        config.paused = paused;
    200 0
        targetHub.updateSpokeConfig(assetId, spoke, config);
    201
      }
    202
    203
      /// @inheritdoc IHubConfigurator
    204 0
      function updateSpokeSupplyCap(
    205
        address hub,
    206
        uint256 assetId,
    207
        address spoke,
    208
        uint256 addCap
    209
      ) external onlyOwner {
    210 0
        IHub targetHub = IHub(hub);
    211 0
        IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    212 0
        config.addCap = addCap.toUint40();
    213 0
        targetHub.updateSpokeConfig(assetId, spoke, config);
    214
      }
    215
    216
      /// @inheritdoc IHubConfigurator
    217 0
      function updateSpokeDrawCap(
    218
        address hub,
    219
        uint256 assetId,
    220
        address spoke,
    221
        uint256 drawCap
    222
      ) external onlyOwner {
    223 0
        IHub targetHub = IHub(hub);
    224 0
        IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    225 0
        config.drawCap = drawCap.toUint40();
    226 0
        targetHub.updateSpokeConfig(assetId, spoke, config);
    227
      }
    228
    229
      /// @inheritdoc IHubConfigurator
    230 0
      function updateSpokeRiskPremiumThreshold(
    231
        address hub,
    232
        uint256 assetId,
    233
        address spoke,
    234
        uint256 riskPremiumThreshold
    235
      ) external onlyOwner {
    236 0
        IHub targetHub = IHub(hub);
    237 0
        IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    238 0
        config.riskPremiumThreshold = riskPremiumThreshold.toUint24();
    239 0
        targetHub.updateSpokeConfig(assetId, spoke, config);
    240
      }
    241
    242
      /// @inheritdoc IHubConfigurator
    243 0
      function updateSpokeCaps(
    244
        address hub,
    245
        uint256 assetId,
    246
        address spoke,
    247
        uint256 addCap,
    248
        uint256 drawCap
    249
      ) external onlyOwner {
    250 0
        _updateSpokeCaps(IHub(hub), assetId, spoke, addCap, drawCap);
    251
      }
    252
    253
      /// @inheritdoc IHubConfigurator
    254 0
      function deactivateSpoke(address hub, address spoke) external onlyOwner {
    255 0
        IHub targetHub = IHub(hub);
    256 0
        uint256 assetCount = targetHub.getAssetCount();
    257 0
        for (uint256 assetId = 0; assetId < assetCount; ++assetId) {
    258 0
          if (targetHub.isSpokeListed(assetId, spoke)) {
    259 0
            IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    260 0
            config.active = false;
    261 0
            targetHub.updateSpokeConfig(assetId, spoke, config);
    262
          }
    263
        }
    264
      }
    265
    266
      /// @inheritdoc IHubConfigurator
    267 0
      function pauseSpoke(address hub, address spoke) external onlyOwner {
    268 0
        IHub targetHub = IHub(hub);
    269 0
        uint256 assetCount = targetHub.getAssetCount();
    270 0
        for (uint256 assetId = 0; assetId < assetCount; ++assetId) {
    271 0
          if (targetHub.isSpokeListed(assetId, spoke)) {
    272 0
            IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    273 0
            config.paused = true;
    274 0
            targetHub.updateSpokeConfig(assetId, spoke, config);
    275
          }
    276
        }
    277
      }
    278
    279
      /// @inheritdoc IHubConfigurator
    280 0
      function freezeSpoke(address hub, address spoke) external onlyOwner {
    281 0
        IHub targetHub = IHub(hub);
    282 0
        uint256 assetCount = targetHub.getAssetCount();
    283 0
        for (uint256 assetId = 0; assetId < assetCount; ++assetId) {
    284 0
          if (targetHub.isSpokeListed(assetId, spoke)) {
    285 0
            IHub.SpokeConfig memory config = targetHub.getSpokeConfig(assetId, spoke);
    286 0
            config.addCap = 0;
    287 0
            config.drawCap = 0;
    288 0
            targetHub.updateSpokeConfig(assetId, spoke, config);
    289
          }
    290
        }
    291
      }
    292
    293
      /// @inheritdoc IHubConfigurator
    294 0
      function updateInterestRateData(
    295
        address hub,
    296
        uint256 assetId,
    297
        bytes calldata irData
    298
      ) external onlyOwner {
    299 0
        IHub(hub).setInterestRateData(assetId, irData);
    300
      }
    301
    302
      /// @dev Updates spoke caps without changing the active flag.
    303 0
      function _updateSpokeCaps(
    304
        IHub hub,
    305
        uint256 assetId,
    306
        address spoke,
    307
        uint256 addCap,
    308
        uint256 drawCap
    309
      ) internal {
    310 0
        IHub.SpokeConfig memory config = hub.getSpokeConfig(assetId, spoke);
    311 0
        config.addCap = addCap.toUint40();
    312 0
        config.drawCap = drawCap.toUint40();
    313 0
        hub.updateSpokeConfig(assetId, spoke, config);
    314
      }
    315
    316 0
      function _updateLiquidityFee(IHub hub, uint256 assetId, uint256 liquidityFee) internal {
    317 0
        IHub.AssetConfig memory config = hub.getAssetConfig(assetId);
    318 0
        config.liquidityFee = liquidityFee.toUint16();
    319 0
        hub.updateAssetConfig(assetId, config, new bytes(0));
    320
      }
    321
    }
    95% src/hub/libraries/AssetLogic.sol
    Lines covered: 96 / 101 (95%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    6
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    7
    import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
    8
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    9
    import {SharesMath} from 'src/hub/libraries/SharesMath.sol';
    10
    import {Premium} from 'src/hub/libraries/Premium.sol';
    11
    import {IBasicInterestRateStrategy} from 'src/hub/interfaces/IBasicInterestRateStrategy.sol';
    12
    import {IHub} from 'src/hub/interfaces/IHub.sol';
    13
    14
    /// @title AssetLogic library
    15
    /// @author Aave Labs
    16
    /// @notice Implements the base logic and share price conversions for asset data.
    17 0
    library AssetLogic {
    18
      using AssetLogic for IHub.Asset;
    19
      using SafeCast for uint256;
    20
      using MathUtils for uint256;
    21
      using PercentageMath for uint256;
    22
      using WadRayMath for *;
    23
      using SharesMath for uint256;
    24
    25
      /// @notice Converts an amount of shares to the equivalent amount of drawn assets, rounding up.
    26
      function toDrawnAssetsUp(
    27
        IHub.Asset storage asset,
    28
        uint256 shares
    29
      ) internal view returns (uint256) {
    30 10×
        return shares.rayMulUp(asset.getDrawnIndex());
    31
      }
    32
    33
      /// @notice Converts an amount of shares to the equivalent amount of drawn assets, rounding down.
    34 0
      function toDrawnAssetsDown(
    35
        IHub.Asset storage asset,
    36
        uint256 shares
    37 0
      ) internal view returns (uint256) {
    38 0
        return shares.rayMulDown(asset.getDrawnIndex());
    39
      }
    40
    41
      /// @notice Converts an amount of drawn assets to the equivalent amount of shares, rounding up.
    42
      function toDrawnSharesUp(
    43
        IHub.Asset storage asset,
    44
        uint256 assets
    45
      ) internal view returns (uint256) {
    46 10×
        return assets.rayDivUp(asset.getDrawnIndex());
    47
      }
    48
    49
      /// @notice Converts an amount of drawn assets to the equivalent amount of shares, rounding down.
    50
      function toDrawnSharesDown(
    51
        IHub.Asset storage asset,
    52
        uint256 assets
    53
      ) internal view returns (uint256) {
    54 20×
        return assets.rayDivDown(asset.getDrawnIndex());
    55
      }
    56
    57
      /// @notice Returns the total drawn assets amount for the specified asset.
    58
      function drawn(IHub.Asset storage asset, uint256 drawnIndex) internal view returns (uint256) {
    59 30×
        return asset.drawnShares.rayMulUp(drawnIndex);
    60
      }
    61
    62
      /// @notice Returns the total premium amount for the specified asset.
    63
      function premium(IHub.Asset storage asset, uint256 drawnIndex) internal view returns (uint256) {
    64
        return
    65
          Premium
    66
            .calculatePremiumRay({
    67
              premiumShares: asset.premiumShares,
    68
              drawnIndex: drawnIndex,
    69
              premiumOffsetRay: asset.premiumOffsetRay
    70
            })
    71
            .fromRayUp();
    72
      }
    73
    74
      /// @notice Returns the total amount owed for the specified asset, including drawn and premium.
    75
      function totalOwed(IHub.Asset storage asset, uint256 drawnIndex) internal view returns (uint256) {
    76 17×
        return asset.drawn(drawnIndex) + asset.premium(drawnIndex);
    77
      }
    78
    79
      /// @notice Returns the total added assets for the specified asset.
    80
      function totalAddedAssets(IHub.Asset storage asset) internal view returns (uint256) {
    81 27×
        uint256 drawnIndex = asset.getDrawnIndex();
    82
    83 24×
        uint256 aggregatedOwedRay = _calculateAggregatedOwedRay({
    84 24×
          drawnShares: asset.drawnShares,
    85 15×
          premiumShares: asset.premiumShares,
    86 30×
          premiumOffsetRay: asset.premiumOffsetRay,
    87 15×
          deficitRay: asset.deficitRay,
    88
          drawnIndex: drawnIndex
    89
        });
    90
    91
        return
    92 78×
          asset.liquidity +
    93 30×
          asset.swept +
    94 15×
          aggregatedOwedRay.fromRayUp() -
    95 15×
          asset.realizedFees -
    96 18×
          asset.getUnrealizedFees(drawnIndex);
    97
      }
    98
    99
      /// @notice Converts an amount of shares to the equivalent amount of added assets, rounding up.
    100
      function toAddedAssetsUp(
    101
        IHub.Asset storage asset,
    102
        uint256 shares
    103
      ) internal view returns (uint256) {
    104 16×
        return shares.toAssetsUp(asset.totalAddedAssets(), asset.addedShares);
    105
      }
    106
    107
      /// @notice Converts an amount of shares to the equivalent amount of added assets, rounding down.
    108
      function toAddedAssetsDown(
    109
        IHub.Asset storage asset,
    110
        uint256 shares
    111
      ) internal view returns (uint256) {
    112 32×
        return shares.toAssetsDown(asset.totalAddedAssets(), asset.addedShares);
    113
      }
    114
    115
      /// @notice Converts an amount of added assets to the equivalent amount of shares, rounding up.
    116
      function toAddedSharesUp(
    117
        IHub.Asset storage asset,
    118
        uint256 assets
    119
      ) internal view returns (uint256) {
    120 32×
        return assets.toSharesUp(asset.totalAddedAssets(), asset.addedShares);
    121
      }
    122
    123
      /// @notice Converts an amount of added assets to the equivalent amount of shares, rounding down.
    124
      function toAddedSharesDown(
    125
        IHub.Asset storage asset,
    126
        uint256 assets
    127
      ) internal view returns (uint256) {
    128 48×
        return assets.toSharesDown(asset.totalAddedAssets(), asset.addedShares);
    129
      }
    130
    131
      /// @notice Updates the drawn rate of a specified asset.
    132
      /// @dev Premium debt is not used in the interest rate calculation.
    133
      /// @dev Uses last stored index; asset accrual should have already occurred.
    134
      /// @dev Imprecision from downscaling `deficitRay` does not accumulate.
    135 18×
      function updateDrawnRate(IHub.Asset storage asset, uint256 assetId) internal {
    136 36×
        uint256 drawnIndex = asset.drawnIndex;
    137 198×
        uint256 newDrawnRate = IBasicInterestRateStrategy(asset.irStrategy).calculateInterestRate({
    138
          assetId: assetId,
    139
          liquidity: asset.liquidity,
    140 12×
          drawn: asset.drawn(drawnIndex),
    141 30×
          deficit: asset.deficitRay.fromRayUp(),
    142 21×
          swept: asset.swept
    143
        });
    144 60×
        asset.drawnRate = newDrawnRate.toUint96();
    145
    146 63×
        emit IHub.UpdateAsset(assetId, drawnIndex, newDrawnRate, asset.realizedFees);
    147
      }
    148
    149
      /// @notice Accrues interest and fees for the specified asset.
    150 18×
      function accrue(IHub.Asset storage asset) internal {
    151 42×
        if (asset.lastUpdateTimestamp == block.timestamp) {
    152
          return;
    153
        }
    154
    155 24×
        uint256 drawnIndex = asset.getDrawnIndex();
    156 120×
        asset.realizedFees += asset.getUnrealizedFees(drawnIndex).toUint120();
    157 63×
        asset.drawnIndex = drawnIndex.toUint120();
    158 81×
        asset.lastUpdateTimestamp = block.timestamp.toUint40();
    159
      }
    160
    161
      /// @notice Calculates the drawn index of a specified asset based on the existing drawn rate and index.
    162 21×
      function getDrawnIndex(IHub.Asset storage asset) internal view returns (uint256) {
    163 21×
        uint256 previousIndex = asset.drawnIndex;
    164 12×
        uint40 lastUpdateTimestamp = asset.lastUpdateTimestamp;
    165 12×
        if (
    166 75×
          lastUpdateTimestamp == block.timestamp || (asset.drawnShares == 0 && asset.premiumShares == 0)
    167
        ) {
    168
          return previousIndex;
    169
        }
    170
        return
    171 21×
          previousIndex.rayMulUp(
    172 39×
            MathUtils.calculateLinearInterest(asset.drawnRate, lastUpdateTimestamp)
    173
          );
    174
      }
    175
    176
      /// @notice Calculates the amount of fees derived from the index growth due to interest accrual.
    177
      /// @param drawnIndex The current drawn index.
    178
      function getUnrealizedFees(
    179
        IHub.Asset storage asset,
    180
        uint256 drawnIndex
    181
      ) internal view returns (uint256) {
    182 15×
        uint256 previousIndex = asset.drawnIndex;
    183 18×
        if (previousIndex == drawnIndex) {
    184 18×
          return 0;
    185
        }
    186
    187 27×
        uint256 liquidityFee = asset.liquidityFee;
    188 18×
        if (liquidityFee == 0) {
    189 0
          return 0;
    190
        }
    191
    192 30×
        uint120 drawnShares = asset.drawnShares;
    193 15×
        uint120 premiumShares = asset.premiumShares;
    194 24×
        int256 premiumOffsetRay = asset.premiumOffsetRay;
    195 18×
        uint256 deficitRay = asset.deficitRay;
    196
    197 18×
        uint256 aggregatedOwedRayAfter = _calculateAggregatedOwedRay({
    198
          drawnShares: drawnShares,
    199
          premiumShares: premiumShares,
    200
          premiumOffsetRay: premiumOffsetRay,
    201
          deficitRay: deficitRay,
    202
          drawnIndex: drawnIndex
    203
        });
    204
    205 27×
        uint256 aggregatedOwedRayBefore = _calculateAggregatedOwedRay({
    206
          drawnShares: drawnShares,
    207
          premiumShares: premiumShares,
    208
          premiumOffsetRay: premiumOffsetRay,
    209
          deficitRay: deficitRay,
    210
          drawnIndex: previousIndex
    211
        });
    212
    213
        return
    214 63×
          (aggregatedOwedRayAfter.fromRayUp() - aggregatedOwedRayBefore.fromRayUp()).percentMulDown(
    215
            liquidityFee
    216
          );
    217
      }
    218
    219
      /// @notice Calculates the aggregated owed amount for a specified asset, expressed in asset units and scaled by RAY.
    220
      function _calculateAggregatedOwedRay(
    221
        uint256 drawnShares,
    222
        uint256 premiumShares,
    223
        int256 premiumOffsetRay,
    224
        uint256 deficitRay,
    225
        uint256 drawnIndex
    226
      ) internal pure returns (uint256) {
    227 21×
        uint256 premiumRay = Premium.calculatePremiumRay({
    228
          premiumShares: premiumShares,
    229
          premiumOffsetRay: premiumOffsetRay,
    230
          drawnIndex: drawnIndex
    231
        });
    232 60×
        return (drawnShares * drawnIndex) + premiumRay + deficitRay;
    233
      }
    234
    }
    75% src/hub/libraries/Premium.sol
    Lines covered: 3 / 4 (75%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    6
    7
    /// @title Premium library
    8
    /// @author Aave Labs
    9
    /// @notice Implements the premium calculations.
    10 0
    library Premium {
    11
      using SafeCast for *;
    12
    13
      /// @notice Calculates the premium debt with full precision.
    14
      /// @param premiumShares The number of premium shares.
    15
      /// @param premiumOffsetRay The premium offset, expressed in asset units and scaled by RAY.
    16
      /// @param drawnIndex The current drawn index.
    17
      /// @return The premium debt, expressed in asset units and scaled by RAY.
    18
      function calculatePremiumRay(
    19
        uint256 premiumShares,
    20
        int256 premiumOffsetRay,
    21
        uint256 drawnIndex
    22
      ) internal pure returns (uint256) {
    23 100×
        return ((premiumShares * drawnIndex).toInt256() - premiumOffsetRay).toUint256();
    24
      }
    25
    }
    93% src/hub/libraries/SharesMath.sol
    Lines covered: 14 / 15 (93%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    6
    7
    /// @title SharesMath library
    8
    /// @author Aave Labs
    9
    /// @notice Implements the logic to convert between assets and shares.
    10
    /// @dev Utilizes virtual assets and shares to mitigate share manipulation attacks.
    11 0
    library SharesMath {
    12
      using MathUtils for uint256;
    13
    14
      uint256 internal constant VIRTUAL_ASSETS = 1e6;
    15
      uint256 internal constant VIRTUAL_SHARES = 1e6;
    16
    17
      /// @notice Converts an amount of assets to the equivalent amount of shares, rounding down.
    18
      function toSharesDown(
    19
        uint256 assets,
    20
        uint256 totalAssets,
    21
        uint256 totalShares
    22
      ) internal pure returns (uint256) {
    23 48×
        return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
    24
      }
    25
    26
      /// @notice Converts an amount of shares to the equivalent amount of assets, rounding down.
    27
      function toAssetsDown(
    28
        uint256 shares,
    29
        uint256 totalAssets,
    30
        uint256 totalShares
    31
      ) internal pure returns (uint256) {
    32 20×
        return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
    33
      }
    34
    35
      /// @notice Converts an amount of assets to the equivalent amount of shares, rounding up.
    36
      function toSharesUp(
    37
        uint256 assets,
    38
        uint256 totalAssets,
    39
        uint256 totalShares
    40
      ) internal pure returns (uint256) {
    41 20×
        return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
    42
      }
    43
    44
      /// @notice Converts an amount of shares to the equivalent amount of assets, rounding up.
    45
      function toAssetsUp(
    46
        uint256 shares,
    47
        uint256 totalAssets,
    48
        uint256 totalShares
    49
      ) internal pure returns (uint256) {
    50 22×
        return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
    51
      }
    52
    }
    81% src/libraries/math/MathUtils.sol
    Lines covered: 26 / 32 (81%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    /// @title MathUtils library
    6
    /// @author Aave Labs
    7 0
    library MathUtils {
    8
      uint256 internal constant RAY = 1e27;
    9
      /// @dev Ignoring leap years
    10 0
      uint256 internal constant SECONDS_PER_YEAR = 365 days;
    11
    12
      /// @notice Calculates the interest accumulated using a linear interest rate formula.
    13
      /// @dev Reverts if `lastUpdateTimestamp` is greater than `block.timestamp`.
    14
      /// @param rate The interest rate in RAY units.
    15
      /// @param lastUpdateTimestamp The timestamp to calculate interest rate from.
    16
      /// @return result The interest rate linearly accumulated during the time delta in RAY units.
    17
      function calculateLinearInterest(
    18
        uint96 rate,
    19
        uint40 lastUpdateTimestamp
    20
      ) internal view returns (uint256 result) {
    21
        assembly ('memory-safe') {
    22 21×
          if gt(lastUpdateTimestamp, timestamp()) {
    23 0
            revert(0, 0)
    24
          }
    25 15×
          result := sub(timestamp(), lastUpdateTimestamp)
    26 24×
          result := add(div(mul(rate, result), SECONDS_PER_YEAR), RAY)
    27
        }
    28
      }
    29
    30
      /// @notice Returns the smaller of two unsigned integers.
    31
      function min(uint256 a, uint256 b) internal pure returns (uint256 result) {
    32
        assembly ('memory-safe') {
    33 17×
          result := xor(b, mul(xor(a, b), lt(a, b)))
    34
        }
    35
      }
    36
    37
      /// @notice Returns the sum of an unsigned and signed integer.
    38
      /// @dev Reverts on underflow.
    39
      function add(uint256 a, int256 b) internal pure returns (uint256) {
    40 64×
        if (b >= 0) return a + uint256(b);
    41 40×
        return a - uint256(-b);
    42
      }
    43
    44
      /// @notice Returns the sum of two unsigned integers.
    45
      /// @dev Does not revert on overflow.
    46
      function uncheckedAdd(uint256 a, uint256 b) internal pure returns (uint256) {
    47
        unchecked {
    48
          return a + b;
    49
        }
    50
      }
    51
    52
      /// @notice Returns the difference of two unsigned integers as a signed integer.
    53
      /// @dev Does not ensure the `a` and `b` values are within the range of a signed integer.
    54
      function signedSub(uint256 a, uint256 b) internal pure returns (int256) {
    55 10×
        return int256(a) - int256(b);
    56
      }
    57
    58
      /// @notice Returns the difference of two unsigned integers.
    59
      /// @dev Does not revert on underflow.
    60
      function uncheckedSub(uint256 a, uint256 b) internal pure returns (uint256) {
    61
        unchecked {
    62 25×
          return a - b;
    63
        }
    64
      }
    65
    66
      /// @notice Raises an unsigned integer to the power of an unsigned integer.
    67
      /// @dev Does not revert on overflow.
    68
      function uncheckedExp(uint256 a, uint256 b) internal pure returns (uint256) {
    69
        unchecked {
    70
          return a ** b;
    71
        }
    72
      }
    73
    74
      /// @notice Multiplies `a` and `b` in 256 bits and divides the result by `c`, rounding down.
    75
      /// @dev Reverts if division by zero or overflow occurs on intermediate multiplication.
    76
      /// @return d = floor(a * b / c).
    77 12×
      function mulDivDown(uint256 a, uint256 b, uint256 c) internal pure returns (uint256 d) {
    78
        // to avoid overflow, a <= type(uint256).max / b
    79
        assembly ('memory-safe') {
    80 16×
          if iszero(c) {
    81 0
            revert(0, 0)
    82
          }
    83 52×
          if iszero(or(iszero(b), iszero(gt(a, div(not(0), b))))) {
    84 0
            revert(0, 0)
    85
          }
    86 16×
          d := div(mul(a, b), c)
    87
        }
    88
      }
    89
    90
      /// @notice Multiplies `a` and `b` in 256 bits and divides the result by `c`, rounding up.
    91
      /// @dev Reverts if division by zero or overflow occurs on intermediate multiplication.
    92
      /// @return d = ceil(a * b / c).
    93
      function mulDivUp(uint256 a, uint256 b, uint256 c) internal pure returns (uint256 d) {
    94
        // to avoid overflow, a <= type(uint256).max / b
    95
        assembly ('memory-safe') {
    96 12×
          if iszero(c) {
    97 0
            revert(0, 0)
    98
          }
    99 39×
          if iszero(or(iszero(b), iszero(gt(a, div(not(0), b))))) {
    100
            revert(0, 0)
    101
          }
    102
          d := mul(a, b)
    103
          // add 1 if (a * b) % c > 0 to round up the division of (a * b) by c
    104 30×
          d := add(div(d, c), gt(mod(d, c), 0))
    105
        }
    106
      }
    107
    }
    45% src/libraries/math/PercentageMath.sol
    Lines covered: 11 / 24 (45%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    /// @title PercentageMath library
    6
    /// @author Aave Labs
    7
    /// @notice Provides functions to perform percentage calculations with explicit rounding.
    8
    /// @dev Percentages are defined by default with 2 decimals of precision (100.00). The precision is indicated by `PERCENTAGE_FACTOR`.
    9 0
    library PercentageMath {
    10
      // Maximum percentage factor in BPS (100.00%)
    11 13×
      uint256 internal constant PERCENTAGE_FACTOR = 1e4;
    12
    13
      /// @notice Executes a percentage multiplication, rounded down.
    14
      /// @dev Reverts if intermediate multiplication overflows.
    15
      /// @return result = floor(value * percentage / PERCENTAGE_FACTOR)
    16
      function percentMulDown(
    17
        uint256 value,
    18
        uint256 percentage
    19
      ) internal pure returns (uint256 result) {
    20
        // to avoid overflow, value <= type(uint256).max / percentage
    21
        assembly ('memory-safe') {
    22 52×
          if iszero(or(iszero(percentage), iszero(gt(value, div(not(0), percentage))))) {
    23 0
            revert(0, 0)
    24
          }
    25
    26 20×
          result := div(mul(value, percentage), PERCENTAGE_FACTOR)
    27
        }
    28
      }
    29
    30
      /// @notice Executes a percentage multiplication, rounded up.
    31
      /// @dev Reverts if intermediate multiplication overflows.
    32
      /// @return result = ceil(value * percentage / PERCENTAGE_FACTOR)
    33 12×
      function percentMulUp(uint256 value, uint256 percentage) internal pure returns (uint256 result) {
    34
        // to avoid overflow, value <= type(uint256).max / percentage
    35
        assembly ('memory-safe') {
    36 52×
          if iszero(or(iszero(percentage), iszero(gt(value, div(not(0), percentage))))) {
    37 0
            revert(0, 0)
    38
          }
    39
          result := mul(value, percentage)
    40
    41
          // Add 1 if (value * percentage) % PERCENTAGE_FACTOR > 0 to round up the division of (value * percentage) by PERCENTAGE_FACTOR
    42 44×
          result := add(div(result, PERCENTAGE_FACTOR), gt(mod(result, PERCENTAGE_FACTOR), 0))
    43
        }
    44
      }
    45
    46
      /// @notice Executes a percentage division, rounded down.
    47
      /// @dev Reverts if division by zero or intermediate multiplication overflows.
    48
      /// @return result = floor(value * PERCENTAGE_FACTOR / percentage)
    49 0
      function percentDivDown(
    50
        uint256 value,
    51
        uint256 percentage
    52 0
      ) internal pure returns (uint256 result) {
    53
        // to avoid overflow, value <= type(uint256).max / PERCENTAGE_FACTOR
    54
        assembly ('memory-safe') {
    55 0
          if or(iszero(percentage), iszero(iszero(gt(value, div(not(0), PERCENTAGE_FACTOR))))) {
    56 0
            revert(0, 0)
    57
          }
    58
    59 0
          result := div(mul(value, PERCENTAGE_FACTOR), percentage)
    60
        }
    61
      }
    62
    63
      /// @notice Executes a percentage division, rounded up.
    64
      /// @dev Reverts if division by zero or intermediate multiplication overflows.
    65
      /// @return result = ceil(value * PERCENTAGE_FACTOR / percentage)
    66 0
      function percentDivUp(uint256 value, uint256 percentage) internal pure returns (uint256 result) {
    67
        // to avoid overflow, value <= type(uint256).max / PERCENTAGE_FACTOR
    68
        assembly ('memory-safe') {
    69 0
          if or(iszero(percentage), iszero(iszero(gt(value, div(not(0), PERCENTAGE_FACTOR))))) {
    70 0
            revert(0, 0)
    71
          }
    72 0
          result := mul(value, PERCENTAGE_FACTOR)
    73
    74
          // Add 1 if (value * PERCENTAGE_FACTOR) % percentage > 0 to round up the division of (value * PERCENTAGE_FACTOR) by percentage
    75 0
          result := add(div(result, percentage), gt(mod(result, percentage), 0))
    76
        }
    77
      }
    78
    79
      /// @notice Truncates number from BPS precision, rounding down.
    80
      function fromBpsDown(uint256 value) internal pure returns (uint256) {
    81
        return value / PERCENTAGE_FACTOR;
    82
      }
    83
    }
    54% src/libraries/math/WadRayMath.sol
    Lines covered: 34 / 62 (54%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    /// @title WadRayMath library
    6
    /// @author Aave Labs
    7
    /// @notice Provides utility functions to work with WAD and RAY units with explicit rounding.
    8 0
    library WadRayMath {
    9 0
      uint256 internal constant WAD = 1e18;
    10 0
      uint256 internal constant RAY = 1e27;
    11 0
      uint256 internal constant PERCENTAGE_FACTOR = 1e4;
    12
    13
      /// @notice Multiplies two WAD numbers, rounding down.
    14
      /// @dev Reverts if intermediate multiplication overflows.
    15
      /// @return c = floor(a * b / WAD) in WAD units.
    16 0
      function wadMulDown(uint256 a, uint256 b) internal pure returns (uint256 c) {
    17
        assembly ('memory-safe') {
    18
          // to avoid overflow, a <= type(uint256).max / b
    19 0
          if iszero(or(iszero(b), iszero(gt(a, div(not(0), b))))) {
    20 0
            revert(0, 0)
    21
          }
    22
    23 0
          c := div(mul(a, b), WAD)
    24
        }
    25
      }
    26
    27
      /// @notice Multiplies two WAD numbers, rounding up.
    28
      /// @dev Reverts if intermediate multiplication overflows.
    29
      /// @return c = ceil(a * b / WAD) in WAD units.
    30 0
      function wadMulUp(uint256 a, uint256 b) internal pure returns (uint256 c) {
    31
        assembly ('memory-safe') {
    32
          // to avoid overflow, a <= type(uint256).max / b
    33 0
          if iszero(or(iszero(b), iszero(gt(a, div(not(0), b))))) {
    34 0
            revert(0, 0)
    35
          }
    36 0
          c := mul(a, b)
    37
          // Add 1 if (a * b) % WAD > 0 to round up the division of (a * b) by WAD
    38 0
          c := add(div(c, WAD), gt(mod(c, WAD), 0))
    39
        }
    40
      }
    41
    42
      /// @notice Divides two WAD numbers, rounding down.
    43
      /// @dev Reverts if division by zero or intermediate multiplication overflows.
    44
      /// @return c = floor(a * WAD / b) in WAD units.
    45
      function wadDivDown(uint256 a, uint256 b) internal pure returns (uint256 c) {
    46
        assembly ('memory-safe') {
    47
          // to avoid overflow, a <= type(uint256).max / WAD
    48 10×
          if or(iszero(b), iszero(iszero(gt(a, div(not(0), WAD))))) {
    49 0
            revert(0, 0)
    50
          }
    51
    52
          c := div(mul(a, WAD), b)
    53
        }
    54
      }
    55
    56
      /// @notice Divides two WAD numbers, rounding up.
    57
      /// @dev Reverts if division by zero or intermediate multiplication overflows.
    58
      /// @return c = ceil(a * WAD / b) in WAD units.
    59
      function wadDivUp(uint256 a, uint256 b) internal pure returns (uint256 c) {
    60
        assembly ('memory-safe') {
    61
          // to avoid overflow, a <= type(uint256).max / WAD
    62 10×
          if or(iszero(b), iszero(iszero(gt(a, div(not(0), WAD))))) {
    63 0
            revert(0, 0)
    64
          }
    65
          c := mul(a, WAD)
    66
          // Add 1 if (a * WAD) % b > 0 to round up the division of (a * WAD) by b
    67 10×
          c := add(div(c, b), gt(mod(c, b), 0))
    68
        }
    69
      }
    70
    71
      /// @notice Multiplies two RAY numbers, rounding down.
    72
      /// @dev Reverts if intermediate multiplication overflows.
    73
      /// @return c = floor(a * b / RAY) in RAY units.
    74 0
      function rayMulDown(uint256 a, uint256 b) internal pure returns (uint256 c) {
    75
        assembly ('memory-safe') {
    76
          // to avoid overflow, a <= type(uint256).max / b
    77 0
          if iszero(or(iszero(b), iszero(gt(a, div(not(0), b))))) {
    78 0
            revert(0, 0)
    79
          }
    80
    81 0
          c := div(mul(a, b), RAY)
    82
        }
    83
      }
    84
    85
      /// @notice Multiplies two RAY numbers, rounding up.
    86
      /// @dev Reverts if intermediate multiplication overflows.
    87
      /// @return c = ceil(a * b / RAY) in RAY units.
    88 21×
      function rayMulUp(uint256 a, uint256 b) internal pure returns (uint256 c) {
    89
        assembly ('memory-safe') {
    90
          // to avoid overflow, a <= type(uint256).max / b
    91 91×
          if iszero(or(iszero(b), iszero(gt(a, div(not(0), b))))) {
    92 0
            revert(0, 0)
    93
          }
    94 14×
          c := mul(a, b)
    95
          // Add 1 if (a * b) % RAY > 0 to round up the division of (a * b) by RAY
    96 70×
          c := add(div(c, RAY), gt(mod(c, RAY), 0))
    97
        }
    98
      }
    99
    100
      /// @notice Divides two RAY numbers, rounding down.
    101
      /// @dev Reverts if division by zero or intermediate multiplication overflows.
    102
      /// @return c = floor(a * RAY / b) in RAY units.
    103 12×
      function rayDivDown(uint256 a, uint256 b) internal pure returns (uint256 c) {
    104
        assembly ('memory-safe') {
    105
          // to avoid overflow, a <= type(uint256).max / RAY
    106 40×
          if or(iszero(b), iszero(iszero(gt(a, div(not(0), RAY))))) {
    107 0
            revert(0, 0)
    108
          }
    109
    110 24×
          c := div(mul(a, RAY), b)
    111
        }
    112
      }
    113
    114
      /// @notice Divides two RAY numbers, rounding up.
    115
      /// @dev Reverts if division by zero or intermediate multiplication overflows.
    116
      /// @return c = ceil(a * RAY / b) in RAY units.
    117 12×
      function rayDivUp(uint256 a, uint256 b) internal pure returns (uint256 c) {
    118
        assembly ('memory-safe') {
    119
          // to avoid overflow, a <= type(uint256).max / RAY
    120 40×
          if or(iszero(b), iszero(iszero(gt(a, div(not(0), RAY))))) {
    121 0
            revert(0, 0)
    122
          }
    123 16×
          c := mul(a, RAY)
    124
          // Add 1 if (a * RAY) % b > 0 to round up the division of (a * RAY) by b
    125 40×
          c := add(div(c, b), gt(mod(c, b), 0))
    126
        }
    127
      }
    128
    129
      /// @notice Casts value to WAD, adding 18 digits of precision.
    130
      /// @dev Reverts if intermediate multiplication overflows.
    131
      /// @return b = a * WAD in WAD units.
    132
      function toWad(uint256 a) internal pure returns (uint256 b) {
    133
        assembly ('memory-safe') {
    134
          b := mul(a, WAD)
    135
    136
          // to avoid overflow, b/WAD == a
    137
          if iszero(eq(div(b, WAD), a)) {
    138 0
            revert(0, 0)
    139
          }
    140
        }
    141
      }
    142
    143
      /// @notice Casts value to RAY, adding 27 digits of precision.
    144
      /// @dev Reverts if intermediate multiplication overflows.
    145
      /// @return b = a * RAY in RAY units.
    146
      function toRay(uint256 a) internal pure returns (uint256 b) {
    147
        assembly ('memory-safe') {
    148
          b := mul(a, RAY)
    149
    150
          // to avoid overflow, b/RAY == a
    151 13×
          if iszero(eq(div(b, RAY), a)) {
    152 0
            revert(0, 0)
    153
          }
    154
        }
    155
      }
    156
    157
      /// @notice Removes WAD precision from a given value, rounding down.
    158
      /// @return b = a / WAD.
    159 0
      function fromWadDown(uint256 a) internal pure returns (uint256 b) {
    160
        assembly ('memory-safe') {
    161 0
          b := div(a, WAD)
    162
        }
    163
      }
    164
    165
      /// @notice Removes RAY precision from a given value, rounding up.
    166
      /// @return b = ceil(a / RAY).
    167 10×
      function fromRayUp(uint256 a) internal pure returns (uint256 b) {
    168
        assembly ('memory-safe') {
    169
          // add 1 if (a % RAY) > 0 to round up the division of a by RAY
    170 45×
          b := add(div(a, RAY), gt(mod(a, RAY), 0))
    171
        }
    172
      }
    173
    174
      /// @notice Converts value from basis points to WAD, rounding down.
    175
      /// @dev Reverts if intermediate multiplication overflows.
    176
      /// @return b = floor(a * WAD / PERCENTAGE_FACTOR) in WAD units.
    177
      function bpsToWad(uint256 a) internal pure returns (uint256 b) {
    178
        assembly ('memory-safe') {
    179
          b := mul(a, WAD)
    180
    181
          // to avoid overflow, b/WAD == a
    182
          if iszero(eq(div(b, WAD), a)) {
    183 0
            revert(0, 0)
    184
          }
    185
    186
          b := div(b, PERCENTAGE_FACTOR)
    187
        }
    188
      }
    189
    190
      /// @notice Converts value from basis points to RAY, rounding down.
    191
      /// @dev Reverts if intermediate multiplication overflows.
    192
      /// @return b = a * RAY / PERCENTAGE_FACTOR in RAY units.
    193
      function bpsToRay(uint256 a) internal pure returns (uint256 b) {
    194
        assembly ('memory-safe') {
    195 12×
          b := mul(a, RAY)
    196
    197
          // to avoid overflow, b/RAY == a
    198 21×
          if iszero(eq(div(b, RAY), a)) {
    199 0
            revert(0, 0)
    200
          }
    201
    202 12×
          b := div(b, PERCENTAGE_FACTOR)
    203
        }
    204
      }
    205
    }
    0% src/libraries/types/EIP712Types.sol
    Lines covered: 0 / 1 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    /// @title EIP712Types library
    6
    /// @author Aave Labs
    7
    /// @notice Defines type structs used in EIP712-typed signatures.
    8 0
    library EIP712Types {
    9
      struct SetUserPositionManager {
    10
        address positionManager;
    11
        address user;
    12
        bool approve;
    13
        uint256 nonce;
    14
        uint256 deadline;
    15
      }
    16
    17
      struct Permit {
    18
        address owner;
    19
        address spender;
    20
        uint256 value;
    21
        uint256 nonce;
    22
        uint256 deadline;
    23
      }
    24
    25
      struct Supply {
    26
        address spoke;
    27
        uint256 reserveId;
    28
        uint256 amount;
    29
        address onBehalfOf;
    30
        uint256 nonce;
    31
        uint256 deadline;
    32
      }
    33
    34
      struct Withdraw {
    35
        address spoke;
    36
        uint256 reserveId;
    37
        uint256 amount;
    38
        address onBehalfOf;
    39
        uint256 nonce;
    40
        uint256 deadline;
    41
      }
    42
    43
      struct Borrow {
    44
        address spoke;
    45
        uint256 reserveId;
    46
        uint256 amount;
    47
        address onBehalfOf;
    48
        uint256 nonce;
    49
        uint256 deadline;
    50
      }
    51
    52
      struct Repay {
    53
        address spoke;
    54
        uint256 reserveId;
    55
        uint256 amount;
    56
        address onBehalfOf;
    57
        uint256 nonce;
    58
        uint256 deadline;
    59
      }
    60
    61
      struct SetUsingAsCollateral {
    62
        address spoke;
    63
        uint256 reserveId;
    64
        bool useAsCollateral;
    65
        address onBehalfOf;
    66
        uint256 nonce;
    67
        uint256 deadline;
    68
      }
    69
    70
      struct UpdateUserRiskPremium {
    71
        address spoke;
    72
        address user;
    73
        uint256 nonce;
    74
        uint256 deadline;
    75
      }
    76
    77
      struct UpdateUserDynamicConfig {
    78
        address spoke;
    79
        address user;
    80
        uint256 nonce;
    81
        uint256 deadline;
    82
      }
    83
    }
    60% src/libraries/types/Roles.sol
    Lines covered: 3 / 5 (60%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    /// @title Roles library
    6
    /// @author Aave Labs
    7
    /// @notice Defines the different roles used by the protocol.
    8 0
    library Roles {
    9 0
      uint64 public constant DEFAULT_ADMIN_ROLE = 0;
    10
      uint64 public constant HUB_ADMIN_ROLE = 1;
    11
      uint64 public constant SPOKE_ADMIN_ROLE = 2;
    12
      uint64 public constant USER_POSITION_UPDATER_ROLE = 3;
    13
    }
    0% src/misc/UnitPriceFeed.sol
    Lines covered: 0 / 22 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {AggregatorV3Interface} from 'src/dependencies/chainlink/AggregatorV3Interface.sol';
    6
    7
    /// @title UnitPriceFeed contract
    8
    /// @author Aave Labs
    9
    /// @notice Price feed that returns the unit price (1), with decimals precision.
    10
    /// @dev This price feed can be set for reserves that use the base currency as collateral.
    11 0
    contract UnitPriceFeed is AggregatorV3Interface {
    12
      /// @inheritdoc AggregatorV3Interface
    13 0
      uint8 public immutable decimals;
    14
    15
      /// @inheritdoc AggregatorV3Interface
    16 0
      string public description;
    17
    18
      int256 private immutable _units;
    19
    20
      /// @dev Constructor.
    21
      /// @param decimals_ The number of decimals used to represent the unit price.
    22
      /// @param description_ The description of the unit price feed.
    23 0
      constructor(uint8 decimals_, string memory description_) {
    24 0
        decimals = decimals_;
    25 0
        description = description_;
    26 0
        _units = int256(10 ** decimals_);
    27
      }
    28
    29
      /// @inheritdoc AggregatorV3Interface
    30 0
      function version() external pure returns (uint256) {
    31 0
        return 1;
    32
      }
    33
    34
      /// @inheritdoc AggregatorV3Interface
    35 0
      function getRoundData(
    36
        uint80 _roundId
    37
      )
    38
        external
    39
        view
    40
        returns (
    41 0
          uint80 roundId,
    42 0
          int256 answer,
    43 0
          uint256 startedAt,
    44 0
          uint256 updatedAt,
    45 0
          uint80 answeredInRound
    46
        )
    47
      {
    48 0
        if (_roundId <= uint80(block.timestamp)) {
    49 0
          roundId = _roundId;
    50 0
          answer = _units;
    51 0
          startedAt = _roundId;
    52
          updatedAt = _roundId;
    53
          answeredInRound = _roundId;
    54
        }
    55
      }
    56
    57
      /// @inheritdoc AggregatorV3Interface
    58 0
      function latestRoundData()
    59
        external
    60
        view
    61
        returns (
    62
          uint80 roundId,
    63
          int256 answer,
    64
          uint256 startedAt,
    65
          uint256 updatedAt,
    66
          uint80 answeredInRound
    67
        )
    68
      {
    69 0
        roundId = uint80(block.timestamp);
    70 0
        answer = _units;
    71
        startedAt = block.timestamp;
    72
        updatedAt = block.timestamp;
    73
        answeredInRound = roundId;
    74
      }
    75
    }
    0% src/position-manager/GatewayBase.sol
    Lines covered: 0 / 17 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
    6
    import {Rescuable} from 'src/utils/Rescuable.sol';
    7
    import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    8
    import {IGatewayBase} from 'src/position-manager/interfaces/IGatewayBase.sol';
    9
    10
    /// @title GatewayBase
    11
    /// @author Aave Labs
    12
    /// @notice Base implementation for gateway common functionalities.
    13
    abstract contract GatewayBase is IGatewayBase, Rescuable, Ownable2Step {
    14
      mapping(address => bool) internal _registeredSpokes;
    15
    16
      /// @notice Modifier that checks if the specified spoke is registered.
    17
      modifier onlyRegisteredSpoke(address spoke) {
    18 0
        _isSpokeValid(spoke);
    19
        _;
    20
      }
    21
    22
      /// @dev Constructor.
    23
      /// @param initialOwner_ The address of the initial owner.
    24 0
      constructor(address initialOwner_) Ownable(initialOwner_) {}
    25
    26
      /// @inheritdoc IGatewayBase
    27 0
      function registerSpoke(address spoke, bool active) external onlyOwner {
    28 0
        require(spoke != address(0), InvalidAddress());
    29 0
        _registeredSpokes[spoke] = active;
    30 0
        emit SpokeRegistered(spoke, active);
    31
      }
    32
    33
      /// @inheritdoc IGatewayBase
    34 0
      function renouncePositionManagerRole(address spoke, address user) external onlyOwner {
    35 0
        require(user != address(0), InvalidAddress());
    36 0
        ISpoke(spoke).renouncePositionManagerRole(user);
    37
      }
    38
    39
      /// @inheritdoc IGatewayBase
    40 0
      function isSpokeRegistered(address spoke) external view returns (bool) {
    41 0
        return _registeredSpokes[spoke];
    42
      }
    43
    44
      /// @dev Verifies the specified spoke is registered.
    45 0
      function _isSpokeValid(address spoke) internal view {
    46 0
        require(_registeredSpokes[spoke], SpokeNotRegistered());
    47
      }
    48
    49
      /// @return The underlying asset for `reserveId` on the specified spoke.
    50 0
      function _getReserveUnderlying(address spoke, uint256 reserveId) internal view returns (address) {
    51 0
        return ISpoke(spoke).getReserve(reserveId).underlying;
    52
      }
    53
    54
      /// @dev The `owner()` is the allowed caller for Rescuable methods.
    55 0
      function _rescueGuardian() internal view override returns (address) {
    56 0
        return owner();
    57
      }
    58
    }
    0% src/position-manager/NativeTokenGateway.sol
    Lines covered: 0 / 70 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {ReentrancyGuardTransient} from 'src/dependencies/openzeppelin/ReentrancyGuardTransient.sol';
    6
    import {Address} from 'src/dependencies/openzeppelin/Address.sol';
    7
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    8
    import {GatewayBase} from 'src/position-manager/GatewayBase.sol';
    9
    import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    10
    import {INativeWrapper} from 'src/position-manager/interfaces/INativeWrapper.sol';
    11
    import {INativeTokenGateway} from 'src/position-manager/interfaces/INativeTokenGateway.sol';
    12
    13
    /// @title NativeTokenGateway
    14
    /// @author Aave Labs
    15
    /// @notice Gateway to interact with a spoke using the native coin of a chain.
    16
    /// @dev Contract must be an active & approved user position manager in order to execute spoke actions on a user's behalf.
    17 0
    contract NativeTokenGateway is INativeTokenGateway, GatewayBase, ReentrancyGuardTransient {
    18
      using SafeERC20 for *;
    19
    20
      INativeWrapper internal immutable _nativeWrapper;
    21
    22
      /// @dev Constructor.
    23
      /// @param nativeWrapper_ The address of the native wrapper contract.
    24
      /// @param initialOwner_ The address of the initial owner.
    25 0
      constructor(address nativeWrapper_, address initialOwner_) GatewayBase(initialOwner_) {
    26 0
        require(nativeWrapper_ != address(0), InvalidAddress());
    27 0
        _nativeWrapper = INativeWrapper(payable(nativeWrapper_));
    28
      }
    29
    30
      /// @dev Checks only 'nativeWrapper' can transfer native tokens.
    31
      receive() external payable {
    32 0
        require(msg.sender == address(_nativeWrapper), UnsupportedAction());
    33
      }
    34
    35
      /// @dev Unsupported fallback function.
    36
      fallback() external payable {
    37 0
        revert UnsupportedAction();
    38
      }
    39
    40
      /// @inheritdoc INativeTokenGateway
    41 0
      function supplyNative(
    42
        address spoke,
    43
        uint256 reserveId,
    44
        uint256 amount
    45 0
      ) external payable nonReentrant onlyRegisteredSpoke(spoke) returns (uint256, uint256) {
    46 0
        require(msg.value == amount, NativeAmountMismatch());
    47 0
        return _supplyNative(spoke, reserveId, msg.sender, amount);
    48
      }
    49
    50
      /// @inheritdoc INativeTokenGateway
    51 0
      function supplyAsCollateralNative(
    52
        address spoke,
    53
        uint256 reserveId,
    54
        uint256 amount
    55 0
      ) external payable nonReentrant onlyRegisteredSpoke(spoke) returns (uint256, uint256) {
    56 0
        require(msg.value == amount, NativeAmountMismatch());
    57 0
        (uint256 suppliedShares, uint256 suppliedAmount) = _supplyNative(
    58 0
          spoke,
    59 0
          reserveId,
    60 0
          msg.sender,
    61 0
          amount
    62
        );
    63 0
        ISpoke(spoke).setUsingAsCollateral(reserveId, true, msg.sender);
    64
    65 0
        return (suppliedShares, suppliedAmount);
    66
      }
    67
    68
      /// @inheritdoc INativeTokenGateway
    69 0
      function withdrawNative(
    70
        address spoke,
    71
        uint256 reserveId,
    72
        uint256 amount
    73 0
      ) external onlyRegisteredSpoke(spoke) returns (uint256, uint256) {
    74 0
        address underlying = _getReserveUnderlying(spoke, reserveId);
    75 0
        _validateParams(underlying, amount);
    76
    77 0
        (uint256 withdrawnShares, uint256 withdrawnAmount) = ISpoke(spoke).withdraw(
    78 0
          reserveId,
    79 0
          amount,
    80 0
          msg.sender
    81
        );
    82
        _nativeWrapper.withdraw(withdrawnAmount);
    83
        Address.sendValue(payable(msg.sender), withdrawnAmount);
    84
    85
        return (withdrawnShares, withdrawnAmount);
    86
      }
    87
    88
      /// @inheritdoc INativeTokenGateway
    89 0
      function borrowNative(
    90
        address spoke,
    91
        uint256 reserveId,
    92
        uint256 amount
    93 0
      ) external onlyRegisteredSpoke(spoke) returns (uint256, uint256) {
    94 0
        address underlying = _getReserveUnderlying(spoke, reserveId);
    95 0
        _validateParams(underlying, amount);
    96
    97 0
        (uint256 borrowedShares, uint256 borrowedAmount) = ISpoke(spoke).borrow(
    98 0
          reserveId,
    99 0
          amount,
    100 0
          msg.sender
    101
        );
    102 0
        _nativeWrapper.withdraw(borrowedAmount);
    103 0
        Address.sendValue(payable(msg.sender), borrowedAmount);
    104
    105 0
        return (borrowedShares, borrowedAmount);
    106
      }
    107
    108
      /// @inheritdoc INativeTokenGateway
    109 0
      function repayNative(
    110
        address spoke,
    111
        uint256 reserveId,
    112
        uint256 amount
    113 0
      ) external payable nonReentrant onlyRegisteredSpoke(spoke) returns (uint256, uint256) {
    114 0
        require(msg.value == amount, NativeAmountMismatch());
    115 0
        address underlying = _getReserveUnderlying(spoke, reserveId);
    116 0
        _validateParams(underlying, amount);
    117
    118 0
        uint256 userTotalDebt = ISpoke(spoke).getUserTotalDebt(reserveId, msg.sender);
    119 0
        uint256 repayAmount = amount;
    120
        uint256 leftovers;
    121 0
        if (amount > userTotalDebt) {
    122 0
          leftovers = amount - userTotalDebt;
    123 0
          repayAmount = userTotalDebt;
    124
        }
    125
    126 0
        _nativeWrapper.deposit{value: repayAmount}();
    127 0
        _nativeWrapper.forceApprove(spoke, repayAmount);
    128 0
        (uint256 repaidShares, uint256 repaidAmount) = ISpoke(spoke).repay(
    129 0
          reserveId,
    130 0
          repayAmount,
    131 0
          msg.sender
    132
        );
    133
    134 0
        if (leftovers > 0) {
    135 0
          Address.sendValue(payable(msg.sender), leftovers);
    136
        }
    137
    138 0
        return (repaidShares, repaidAmount);
    139
      }
    140
    141
      /// @inheritdoc INativeTokenGateway
    142 0
      function NATIVE_WRAPPER() external view returns (address) {
    143 0
        return address(_nativeWrapper);
    144
      }
    145
    146
      /// @dev `msg.value` verification must be done before calling this.
    147 0
      function _supplyNative(
    148
        address spoke,
    149
        uint256 reserveId,
    150
        address user,
    151
        uint256 amount
    152 0
      ) internal returns (uint256, uint256) {
    153 0
        address underlying = _getReserveUnderlying(spoke, reserveId);
    154 0
        _validateParams(underlying, amount);
    155
    156 0
        _nativeWrapper.deposit{value: amount}();
    157 0
        _nativeWrapper.forceApprove(spoke, amount);
    158 0
        return ISpoke(spoke).supply(reserveId, amount, user);
    159
      }
    160
    161 0
      function _validateParams(address underlying, uint256 amount) internal view {
    162 0
        require(address(_nativeWrapper) == underlying, NotNativeWrappedAsset());
    163 0
        require(amount > 0, InvalidAmount());
    164
      }
    165
    }
    0% src/position-manager/SignatureGateway.sol
    Lines covered: 0 / 111 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol';
    6
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    7
    import {IERC20Permit} from 'src/dependencies/openzeppelin/IERC20Permit.sol';
    8
    import {EIP712} from 'src/dependencies/solady/EIP712.sol';
    9
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    10
    import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol';
    11
    import {Multicall} from 'src/utils/Multicall.sol';
    12
    import {EIP712Hash, EIP712Types} from 'src/position-manager/libraries/EIP712Hash.sol';
    13
    import {GatewayBase} from 'src/position-manager/GatewayBase.sol';
    14
    import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    15
    import {ISignatureGateway} from 'src/position-manager/interfaces/ISignatureGateway.sol';
    16
    17
    /// @title SignatureGateway
    18
    /// @author Aave Labs
    19
    /// @notice Gateway to consume EIP-712 typed intents for spoke actions on behalf of a user.
    20
    /// @dev Contract must be an active & approved user position manager to execute spoke actions on user's behalf.
    21
    /// @dev Uses keyed-nonces where each key's namespace nonce is consumed sequentially. Intents bundled through
    22
    /// multicall can be executed independently in order of signed nonce & deadline; does not guarantee batch atomicity.
    23 0
    contract SignatureGateway is ISignatureGateway, GatewayBase, NoncesKeyed, Multicall, EIP712 {
    24
      using SafeERC20 for IERC20;
    25
      using EIP712Hash for *;
    26
    27
      /// @dev Constructor.
    28
      /// @param initialOwner_ The address of the initial owner.
    29 0
      constructor(address initialOwner_) GatewayBase(initialOwner_) {}
    30
    31
      /// @inheritdoc ISignatureGateway
    32 0
      function supplyWithSig(
    33
        EIP712Types.Supply calldata params,
    34
        bytes calldata signature
    35 0
      ) external onlyRegisteredSpoke(params.spoke) returns (uint256, uint256) {
    36 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    37 0
        address spoke = params.spoke;
    38 0
        uint256 reserveId = params.reserveId;
    39 0
        address user = params.onBehalfOf;
    40 0
        bytes32 digest = _hashTypedData(params.hash());
    41 0
        require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
    42 0
        _useCheckedNonce(user, params.nonce);
    43
    44 0
        IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId));
    45 0
        underlying.safeTransferFrom(user, address(this), params.amount);
    46 0
        underlying.forceApprove(spoke, params.amount);
    47
    48 0
        return ISpoke(spoke).supply(reserveId, params.amount, user);
    49
      }
    50
    51
      /// @inheritdoc ISignatureGateway
    52 0
      function withdrawWithSig(
    53
        EIP712Types.Withdraw calldata params,
    54
        bytes calldata signature
    55 0
      ) external onlyRegisteredSpoke(params.spoke) returns (uint256, uint256) {
    56 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    57 0
        address spoke = params.spoke;
    58 0
        uint256 reserveId = params.reserveId;
    59 0
        address user = params.onBehalfOf;
    60 0
        bytes32 digest = _hashTypedData(params.hash());
    61 0
        require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
    62 0
        _useCheckedNonce(user, params.nonce);
    63
    64 0
        IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId));
    65 0
        (uint256 withdrawnShares, uint256 withdrawnAmount) = ISpoke(spoke).withdraw(
    66 0
          reserveId,
    67 0
          params.amount,
    68 0
          user
    69
        );
    70
        underlying.safeTransfer(user, withdrawnAmount);
    71
    72
        return (withdrawnShares, withdrawnAmount);
    73
      }
    74
    75
      /// @inheritdoc ISignatureGateway
    76 0
      function borrowWithSig(
    77
        EIP712Types.Borrow calldata params,
    78
        bytes calldata signature
    79 0
      ) external onlyRegisteredSpoke(params.spoke) returns (uint256, uint256) {
    80 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    81 0
        address spoke = params.spoke;
    82 0
        uint256 reserveId = params.reserveId;
    83 0
        address user = params.onBehalfOf;
    84 0
        bytes32 digest = _hashTypedData(params.hash());
    85 0
        require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
    86 0
        _useCheckedNonce(user, params.nonce);
    87
    88 0
        IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId));
    89 0
        (uint256 borrowedShares, uint256 borrowedAmount) = ISpoke(spoke).borrow(
    90 0
          reserveId,
    91 0
          params.amount,
    92 0
          user
    93
        );
    94 0
        underlying.safeTransfer(user, borrowedAmount);
    95
    96 0
        return (borrowedShares, borrowedAmount);
    97
      }
    98
    99
      /// @inheritdoc ISignatureGateway
    100 0
      function repayWithSig(
    101
        EIP712Types.Repay calldata params,
    102
        bytes calldata signature
    103 0
      ) external onlyRegisteredSpoke(params.spoke) returns (uint256, uint256) {
    104 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    105 0
        address spoke = params.spoke;
    106 0
        uint256 reserveId = params.reserveId;
    107 0
        address user = params.onBehalfOf;
    108 0
        bytes32 digest = _hashTypedData(params.hash());
    109 0
        require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
    110 0
        _useCheckedNonce(user, params.nonce);
    111
    112 0
        IERC20 underlying = IERC20(_getReserveUnderlying(spoke, reserveId));
    113 0
        uint256 repayAmount = MathUtils.min(
    114 0
          params.amount,
    115 0
          ISpoke(spoke).getUserTotalDebt(reserveId, user)
    116
        );
    117
    118 0
        underlying.safeTransferFrom(user, address(this), repayAmount);
    119 0
        underlying.forceApprove(spoke, repayAmount);
    120
    121 0
        return ISpoke(spoke).repay(reserveId, repayAmount, user);
    122
      }
    123
    124
      /// @inheritdoc ISignatureGateway
    125 0
      function setUsingAsCollateralWithSig(
    126
        EIP712Types.SetUsingAsCollateral calldata params,
    127
        bytes calldata signature
    128 0
      ) external onlyRegisteredSpoke(params.spoke) {
    129 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    130 0
        address user = params.onBehalfOf;
    131 0
        bytes32 digest = _hashTypedData(params.hash());
    132 0
        require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
    133 0
        _useCheckedNonce(user, params.nonce);
    134
    135 0
        ISpoke(params.spoke).setUsingAsCollateral(params.reserveId, params.useAsCollateral, user);
    136
      }
    137
    138
      /// @inheritdoc ISignatureGateway
    139 0
      function updateUserRiskPremiumWithSig(
    140
        EIP712Types.UpdateUserRiskPremium calldata params,
    141
        bytes calldata signature
    142 0
      ) external onlyRegisteredSpoke(params.spoke) {
    143 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    144 0
        bytes32 digest = _hashTypedData(params.hash());
    145 0
        require(
    146 0
          SignatureChecker.isValidSignatureNow(params.user, digest, signature),
    147
          InvalidSignature()
    148
        );
    149 0
        _useCheckedNonce(params.user, params.nonce);
    150
    151 0
        ISpoke(params.spoke).updateUserRiskPremium(params.user);
    152
      }
    153
    154
      /// @inheritdoc ISignatureGateway
    155 0
      function updateUserDynamicConfigWithSig(
    156
        EIP712Types.UpdateUserDynamicConfig calldata params,
    157
        bytes calldata signature
    158 0
      ) external onlyRegisteredSpoke(params.spoke) {
    159 0
        require(block.timestamp <= params.deadline, InvalidSignature());
    160 0
        bytes32 digest = _hashTypedData(params.hash());
    161 0
        require(
    162 0
          SignatureChecker.isValidSignatureNow(params.user, digest, signature),
    163
          InvalidSignature()
    164
        );
    165 0
        _useCheckedNonce(params.user, params.nonce);
    166
    167 0
        ISpoke(params.spoke).updateUserDynamicConfig(params.user);
    168
      }
    169
    170
      /// @inheritdoc ISignatureGateway
    171 0
      function setSelfAsUserPositionManagerWithSig(
    172
        address spoke,
    173
        EIP712Types.SetUserPositionManager calldata params,
    174
        bytes calldata signature
    175 0
      ) external onlyRegisteredSpoke(spoke) {
    176
        try
    177 0
          ISpoke(spoke).setUserPositionManagerWithSig(
    178 0
            address(this),
    179 0
            params.user,
    180 0
            params.approve,
    181 0
            params.nonce,
    182 0
            params.deadline,
    183 0
            signature
    184
          )
    185
        {} catch {}
    186
      }
    187
    188
      /// @inheritdoc ISignatureGateway
    189 0
      function permitReserve(
    190
        address spoke,
    191
        uint256 reserveId,
    192
        address onBehalfOf,
    193
        uint256 value,
    194
        uint256 deadline,
    195
        uint8 permitV,
    196
        bytes32 permitR,
    197
        bytes32 permitS
    198 0
      ) external onlyRegisteredSpoke(spoke) {
    199 0
        address underlying = _getReserveUnderlying(spoke, reserveId);
    200 0
        try
    201 0
          IERC20Permit(underlying).permit({
    202
            owner: onBehalfOf,
    203 0
            spender: address(this),
    204
            value: value,
    205
            deadline: deadline,
    206
            v: permitV,
    207
            r: permitR,
    208
            s: permitS
    209
          })
    210
        {} catch {}
    211
      }
    212
    213
      /// @inheritdoc ISignatureGateway
    214 0
      function DOMAIN_SEPARATOR() external view returns (bytes32) {
    215 0
        return _domainSeparator();
    216
      }
    217
    218
      /// @inheritdoc ISignatureGateway
    219 0
      function SUPPLY_TYPEHASH() external pure returns (bytes32) {
    220
        return EIP712Hash.SUPPLY_TYPEHASH;
    221
      }
    222
    223
      /// @inheritdoc ISignatureGateway
    224 0
      function WITHDRAW_TYPEHASH() external pure returns (bytes32) {
    225
        return EIP712Hash.WITHDRAW_TYPEHASH;
    226
      }
    227
    228
      /// @inheritdoc ISignatureGateway
    229 0
      function BORROW_TYPEHASH() external pure returns (bytes32) {
    230
        return EIP712Hash.BORROW_TYPEHASH;
    231
      }
    232
    233
      /// @inheritdoc ISignatureGateway
    234 0
      function REPAY_TYPEHASH() external pure returns (bytes32) {
    235
        return EIP712Hash.REPAY_TYPEHASH;
    236
      }
    237
    238
      /// @inheritdoc ISignatureGateway
    239 0
      function SET_USING_AS_COLLATERAL_TYPEHASH() external pure returns (bytes32) {
    240
        return EIP712Hash.SET_USING_AS_COLLATERAL_TYPEHASH;
    241
      }
    242
    243
      /// @inheritdoc ISignatureGateway
    244 0
      function UPDATE_USER_RISK_PREMIUM_TYPEHASH() external pure returns (bytes32) {
    245
        return EIP712Hash.UPDATE_USER_RISK_PREMIUM_TYPEHASH;
    246
      }
    247
    248
      /// @inheritdoc ISignatureGateway
    249 0
      function UPDATE_USER_DYNAMIC_CONFIG_TYPEHASH() external pure returns (bytes32) {
    250
        return EIP712Hash.UPDATE_USER_DYNAMIC_CONFIG_TYPEHASH;
    251
      }
    252
    253 0
      function _domainNameAndVersion() internal pure override returns (string memory, string memory) {
    254 0
        return ('SignatureGateway', '1');
    255
      }
    256
    }
    0% src/position-manager/libraries/EIP712Hash.sol
    Lines covered: 0 / 50 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {EIP712Types} from 'src/libraries/types/EIP712Types.sol';
    6
    7
    /// @title EIP712Hash library
    8
    /// @author Aave Labs
    9
    /// @notice Helper methods to hash EIP712 typed data structs.
    10 0
    library EIP712Hash {
    11 0
      bytes32 public constant SUPPLY_TYPEHASH =
    12
        // keccak256('Supply(address spoke,uint256 reserveId,uint256 amount,address onBehalfOf,uint256 nonce,uint256 deadline)')
    13 0
        0xe85497eb293c001e8483fe105efadd1d50aa0dadfc0570b27058031dfceab2e6;
    14
    15 0
      bytes32 public constant WITHDRAW_TYPEHASH =
    16
        // keccak256('Withdraw(address spoke,uint256 reserveId,uint256 amount,address onBehalfOf,uint256 nonce,uint256 deadline)')
    17 0
        0x0bc73eb58cf4068a29b9593ef18c0d26b3b4453bd2155424a90cb26a22f41d7f;
    18
    19 0
      bytes32 public constant BORROW_TYPEHASH =
    20
        // keccak256('Borrow(address spoke,uint256 reserveId,uint256 amount,address onBehalfOf,uint256 nonce,uint256 deadline)')
    21 0
        0xe248895a233688ba2a70b6f560472dbc27e35ece0d86914f7d43bf2f7df8025b;
    22
    23 0
      bytes32 public constant REPAY_TYPEHASH =
    24
        // keccak256('Repay(address spoke,uint256 reserveId,uint256 amount,address onBehalfOf,uint256 nonce,uint256 deadline)')
    25 0
        0xd23fe99a7aac398d03952a098faa8889259d062784bd80ea0f159e4af604c045;
    26
    27 0
      bytes32 public constant SET_USING_AS_COLLATERAL_TYPEHASH =
    28
        // keccak256('SetUsingAsCollateral(address spoke,uint256 reserveId,bool useAsCollateral,address onBehalfOf,uint256 nonce,uint256 deadline)')
    29 0
        0xd4350e1f25ecd62a35b50e8cd1e00bc34331ae8c728ee4dbb69ecf1023daecf7;
    30
    31 0
      bytes32 public constant UPDATE_USER_RISK_PREMIUM_TYPEHASH =
    32
        // keccak256('UpdateUserRiskPremium(address spoke,address user,uint256 nonce,uint256 deadline)')
    33 0
        0xb41e132023782c9b02febf1b9b7fe98c4a73f57ebc63ba44cd71f6365ea09eaf;
    34
    35 0
      bytes32 public constant UPDATE_USER_DYNAMIC_CONFIG_TYPEHASH =
    36
        // keccak256('UpdateUserDynamicConfig(address spoke,address user,uint256 nonce,uint256 deadline)')
    37 0
        0xba177b1f5b5e1e709f62c19f03c97988c57752ba561de58f383ebee4e8d0a71c;
    38
    39 0
      function hash(EIP712Types.Supply calldata params) internal pure returns (bytes32) {
    40
        return
    41
          keccak256(
    42
            abi.encode(
    43
              SUPPLY_TYPEHASH,
    44 0
              params.spoke,
    45
              params.reserveId,
    46
              params.amount,
    47
              params.onBehalfOf,
    48
              params.nonce,
    49
              params.deadline
    50
            )
    51
          );
    52
      }
    53
    54 0
      function hash(EIP712Types.Withdraw calldata params) internal pure returns (bytes32) {
    55
        return
    56
          keccak256(
    57
            abi.encode(
    58
              WITHDRAW_TYPEHASH,
    59 0
              params.spoke,
    60
              params.reserveId,
    61
              params.amount,
    62
              params.onBehalfOf,
    63
              params.nonce,
    64
              params.deadline
    65
            )
    66
          );
    67
      }
    68
    69 0
      function hash(EIP712Types.Borrow calldata params) internal pure returns (bytes32) {
    70
        return
    71
          keccak256(
    72
            abi.encode(
    73
              BORROW_TYPEHASH,
    74 0
              params.spoke,
    75
              params.reserveId,
    76
              params.amount,
    77
              params.onBehalfOf,
    78
              params.nonce,
    79
              params.deadline
    80
            )
    81
          );
    82
      }
    83
    84 0
      function hash(EIP712Types.Repay calldata params) internal pure returns (bytes32) {
    85 0
        return
    86 0
          keccak256(
    87 0
            abi.encode(
    88
              REPAY_TYPEHASH,
    89 0
              params.spoke,
    90 0
              params.reserveId,
    91 0
              params.amount,
    92 0
              params.onBehalfOf,
    93 0
              params.nonce,
    94 0
              params.deadline
    95
            )
    96
          );
    97
      }
    98
    99 0
      function hash(EIP712Types.SetUsingAsCollateral calldata params) internal pure returns (bytes32) {
    100
        return
    101
          keccak256(
    102 0
            abi.encode(
    103
              SET_USING_AS_COLLATERAL_TYPEHASH,
    104 0
              params.spoke,
    105 0
              params.reserveId,
    106 0
              params.useAsCollateral,
    107 0
              params.onBehalfOf,
    108 0
              params.nonce,
    109 0
              params.deadline
    110
            )
    111
          );
    112
      }
    113
    114 0
      function hash(EIP712Types.UpdateUserRiskPremium calldata params) internal pure returns (bytes32) {
    115
        return
    116
          keccak256(
    117
            abi.encode(
    118
              UPDATE_USER_RISK_PREMIUM_TYPEHASH,
    119 0
              params.spoke,
    120
              params.user,
    121
              params.nonce,
    122
              params.deadline
    123
            )
    124
          );
    125
      }
    126
    127 0
      function hash(
    128
        EIP712Types.UpdateUserDynamicConfig calldata params
    129 0
      ) internal pure returns (bytes32) {
    130 0
        return
    131 0
          keccak256(
    132 0
            abi.encode(
    133
              UPDATE_USER_DYNAMIC_CONFIG_TYPEHASH,
    134 0
              params.spoke,
    135 0
              params.user,
    136 0
              params.nonce,
    137 0
              params.deadline
    138
            )
    139
          );
    140
      }
    141
    }
    87% src/spoke/AaveOracle.sol
    Lines covered: 29 / 33 (87%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {AggregatorV3Interface} from 'src/dependencies/chainlink/AggregatorV3Interface.sol';
    6
    import {IAaveOracle, IPriceOracle} from 'src/spoke/interfaces/IAaveOracle.sol';
    7
    8
    /// @title AaveOracle
    9
    /// @author Aave Labs
    10
    /// @notice Provides reserve prices.
    11
    /// @dev Oracles are spoke-specific, due to the usage of reserve id as index of the `_sources` mapping.
    12 194×
    contract AaveOracle is IAaveOracle {
    13
      /// @inheritdoc IPriceOracle
    14
      address public SPOKE;
    15
    16
      /// @inheritdoc IPriceOracle
    17 32×
      uint8 public immutable DECIMALS;
    18
    19
      /// @inheritdoc IAaveOracle
    20 86×
      string public DESCRIPTION;
    21
    22
      mapping(uint256 reserveId => AggregatorV3Interface) internal _sources;
    23
    24
      /// @dev Constructor.
    25
      /// @dev `decimals` must match the spoke's decimals for compatibility.
    26
      /// @param spoke_ The address of the spoke contract.
    27
      /// @param decimals_ The number of decimals for the oracle.
    28
      /// @param description_ The description of the oracle.
    29 30×
      constructor(address spoke_, uint8 decimals_, string memory description_) {
    30
        require(spoke_ != address(0), InvalidAddress());
    31
        SPOKE = spoke_;
    32
        DECIMALS = decimals_;
    33
        DESCRIPTION = description_;
    34
      }
    35
    36
      // @audit FIXME: This is a hack to set the spoke after the oracle is deployed so that it does not rely on vm.computeCreateAddress.
    37
      function setSpoke(address spoke) external {
    38 0
        SPOKE = spoke;
    39
      }
    40
    41
      /// @inheritdoc IAaveOracle
    42 15×
      function setReserveSource(uint256 reserveId, address source) external {
    43
        require(msg.sender == SPOKE, OnlySpoke());
    44
        AggregatorV3Interface targetSource = AggregatorV3Interface(source);
    45 69×
        require(targetSource.decimals() == DECIMALS, InvalidSourceDecimals(reserveId));
    46 19×
        _sources[reserveId] = targetSource;
    47
        _getSourcePrice(reserveId);
    48 12×
        emit UpdateReserveSource(reserveId, source);
    49
      }
    50
    51
      /// @inheritdoc IPriceOracle
    52 36×
      function getReservePrice(uint256 reserveId) external view returns (uint256) {
    53 12×
        return _getSourcePrice(reserveId);
    54
      }
    55
    56
      /// @inheritdoc IAaveOracle
    57
      function getReservesPrices(
    58
        uint256[] calldata reserveIds
    59 0
      ) external view returns (uint256[] memory) {
    60 0
        uint256[] memory prices = new uint256[](reserveIds.length);
    61
        for (uint256 i = 0; i < reserveIds.length; ++i) {
    62 0
          prices[i] = _getSourcePrice(reserveIds[i]);
    63
        }
    64
        return prices;
    65
      }
    66
    67
      /// @inheritdoc IAaveOracle
    68 10×
      function getReserveSource(uint256 reserveId) external view returns (address) {
    69 12×
        return address(_sources[reserveId]);
    70
      }
    71
    72
      /// @dev Price of zero will revert with `InvalidPrice`.
    73
      function _getSourcePrice(uint256 reserveId) internal view returns (uint256) {
    74 24×
        AggregatorV3Interface source = _sources[reserveId];
    75 10×
        require(address(source) != address(0), InvalidSource(reserveId));
    76
    77 130×
        (, int256 price, , , ) = source.latestRoundData();
    78 14×
        require(price > 0, InvalidPrice(reserveId));
    79
    80
        return uint256(price);
    81
      }
    82
    }
    72% src/spoke/Spoke.sol
    Lines covered: 332 / 461 (72%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    6
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    7
    import {IERC20Permit} from 'src/dependencies/openzeppelin/IERC20Permit.sol';
    8
    import {SignatureChecker} from 'src/dependencies/openzeppelin/SignatureChecker.sol';
    9
    import {AccessManagedUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/AccessManagedUpgradeable.sol';
    10
    import {EIP712} from 'src/dependencies/solady/EIP712.sol';
    11
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    12
    import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
    13
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    14
    import {KeyValueList} from 'src/spoke/libraries/KeyValueList.sol';
    15
    import {LiquidationLogic} from 'src/spoke/libraries/LiquidationLogic.sol';
    16
    import {PositionStatusMap} from 'src/spoke/libraries/PositionStatusMap.sol';
    17
    import {ReserveFlags, ReserveFlagsMap} from 'src/spoke/libraries/ReserveFlagsMap.sol';
    18
    import {UserPositionDebt} from 'src/spoke/libraries/UserPositionDebt.sol';
    19
    import {NoncesKeyed} from 'src/utils/NoncesKeyed.sol';
    20
    import {Multicall} from 'src/utils/Multicall.sol';
    21
    import {IAaveOracle} from 'src/spoke/interfaces/IAaveOracle.sol';
    22
    import {IHubBase} from 'src/hub/interfaces/IHubBase.sol';
    23
    import {ISpokeBase, ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    24
    25
    /// @title Spoke
    26
    /// @author Aave Labs
    27
    /// @notice Handles risk configuration & borrowing strategy for reserves and user positions.
    28
    /// @dev Each reserve can be associated with a separate Hub.
    29
    abstract contract Spoke is ISpoke, Multicall, NoncesKeyed, AccessManagedUpgradeable, EIP712 {
    30
      using SafeCast for *;
    31
      using SafeERC20 for IERC20;
    32
      using MathUtils for *;
    33
      using PercentageMath for *;
    34
      using WadRayMath for *;
    35
      using KeyValueList for KeyValueList.List;
    36
      using LiquidationLogic for *;
    37
      using PositionStatusMap for *;
    38
      using ReserveFlagsMap for ReserveFlags;
    39
      using UserPositionDebt for ISpoke.UserPosition;
    40
    41
      /// @inheritdoc ISpoke
    42
      bytes32 public constant SET_USER_POSITION_MANAGER_TYPEHASH =
    43
        // keccak256('SetUserPositionManager(address positionManager,address user,bool approve,uint256 nonce,uint256 deadline)')
    44 0
        0x758d23a3c07218b7ea0b4f7f63903c4e9d5cbde72d3bcfe3e9896639025a0214;
    45
    46
      /// @inheritdoc ISpoke
    47 13×
      address public immutable ORACLE;
    48
    49
      /// @dev The maximum allowed value for an asset identifier (inclusive).
    50 0
      uint256 internal constant MAX_ALLOWED_ASSET_ID = type(uint16).max;
    51
    52
      /// @dev The maximum allowed collateral risk value for a reserve, expressed in BPS (e.g. 100_00 is 100.00%).
    53
      uint24 internal constant MAX_ALLOWED_COLLATERAL_RISK = 1000_00;
    54
    55
      /// @dev The maximum allowed value for a dynamic configuration key (inclusive).
    56
      uint256 internal constant MAX_ALLOWED_DYNAMIC_CONFIG_KEY = type(uint24).max;
    57
    58
      /// @dev The minimum health factor below which a position is considered unhealthy and subject to liquidation.
    59
      /// @dev Expressed in WAD (18 decimals) (e.g. 1e18 is 1.00).
    60
      uint64 internal constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD =
    61
        LiquidationLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
    62
    63
      /// @dev The maximum amount considered as dust for a user's collateral and debt balances after a liquidation.
    64
      /// @dev Expressed in USD with 26 decimals.
    65
      uint256 internal constant DUST_LIQUIDATION_THRESHOLD =
    66
        LiquidationLogic.DUST_LIQUIDATION_THRESHOLD;
    67
    68
      /// @dev The number of decimals used by the oracle.
    69
      uint8 internal constant ORACLE_DECIMALS = 8;
    70
    71
      /// @dev Number of reserves listed in the Spoke.
    72
      uint256 internal _reserveCount;
    73
    74
      /// @dev Map of user addresses and reserve identifiers to user positions.
    75
      mapping(address user => mapping(uint256 reserveId => UserPosition)) internal _userPositions;
    76
    77
      /// @dev Map of user addresses to their position status.
    78
      mapping(address user => PositionStatus) internal _positionStatus;
    79
    80
      /// @dev Map of reserve identifiers to their Reserve data.
    81
      mapping(uint256 reserveId => Reserve) internal _reserves;
    82
    83
      /// @dev Map of position manager addresses to their configuration data.
    84
      mapping(address positionManager => PositionManagerConfig) internal _positionManager;
    85
    86
      /// @dev Map of reserve identifiers and dynamic configuration keys to the dynamic configuration data.
    87
      mapping(uint256 reserveId => mapping(uint24 dynamicConfigKey => DynamicReserveConfig))
    88
        internal _dynamicConfig;
    89
    90
      /// @dev Liquidation configuration for the Spoke.
    91
      LiquidationConfig internal _liquidationConfig;
    92
    93
      /// @dev Map of hub addresses and asset identifiers to whether the reserve exists.
    94
      mapping(address hub => mapping(uint256 assetId => bool)) internal _reserveExists;
    95
    96
      /// @notice Modifier that checks if the caller is an approved positionManager for `onBehalfOf`.
    97
      modifier onlyPositionManager(address onBehalfOf) {
    98 45×
        require(_isPositionManager({user: onBehalfOf, manager: msg.sender}), Unauthorized());
    99
        _;
    100
      }
    101
    102
      /// @dev Constructor.
    103
      /// @param oracle_ The address of the AaveOracle contract.
    104
      constructor(address oracle_) {
    105 65×
        require(IAaveOracle(oracle_).DECIMALS() == ORACLE_DECIMALS, InvalidOracleDecimals());
    106
        ORACLE = oracle_;
    107
      }
    108
    109
      /// @dev To be overridden by the inheriting Spoke instance contract.
    110
      function initialize(address authority) external virtual;
    111
    112
      /// @inheritdoc ISpoke
    113 13×
      function updateLiquidationConfig(LiquidationConfig calldata config) external restricted {
    114 16×
        require(
    115 21×
          config.targetHealthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD &&
    116 14×
            config.liquidationBonusFactor <= PercentageMath.PERCENTAGE_FACTOR &&
    117 12×
            config.healthFactorForMaxBonus < HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
    118
          InvalidLiquidationConfig()
    119
        );
    120 11×
        _liquidationConfig = config;
    121 17×
        emit UpdateLiquidationConfig(config);
    122
      }
    123
    124
      /// @inheritdoc ISpoke
    125 0
      function addReserve(
    126
        address hub,
    127
        uint256 assetId,
    128
        address priceSource,
    129
        ReserveConfig calldata config,
    130
        DynamicReserveConfig calldata dynamicConfig
    131 0
      ) external restricted returns (uint256) {
    132 0
        require(hub != address(0), InvalidAddress());
    133 0
        require(assetId <= MAX_ALLOWED_ASSET_ID, InvalidAssetId());
    134 0
        require(!_reserveExists[hub][assetId], ReserveExists());
    135
    136 0
        _validateReserveConfig(config);
    137 0
        _validateDynamicReserveConfig(dynamicConfig);
    138 0
        uint256 reserveId = _reserveCount++;
    139 0
        uint24 dynamicConfigKey; // 0 as first key to use
    140
    141 0
        (address underlying, uint8 decimals) = IHubBase(hub).getAssetUnderlyingAndDecimals(assetId);
    142 0
        require(underlying != address(0), AssetNotListed());
    143
    144 0
        _updateReservePriceSource(reserveId, priceSource);
    145
    146 0
        _reserves[reserveId] = Reserve({
    147 0
          underlying: underlying,
    148 0
          hub: IHubBase(hub),
    149 0
          assetId: assetId.toUint16(),
    150 0
          decimals: decimals,
    151 0
          dynamicConfigKey: dynamicConfigKey,
    152 0
          collateralRisk: config.collateralRisk,
    153
          flags: ReserveFlagsMap.create({
    154 0
            initPaused: config.paused,
    155 0
            initFrozen: config.frozen,
    156 0
            initBorrowable: config.borrowable,
    157 0
            initLiquidatable: config.liquidatable,
    158
            initReceiveSharesEnabled: config.receiveSharesEnabled
    159
          })
    160
        });
    161 0
        _dynamicConfig[reserveId][dynamicConfigKey] = dynamicConfig;
    162 0
        _reserveExists[hub][assetId] = true;
    163
    164 0
        emit AddReserve(reserveId, assetId, hub);
    165 0
        emit UpdateReserveConfig(reserveId, config);
    166 0
        emit AddDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicConfig);
    167
    168 0
        return reserveId;
    169
      }
    170
    171
      /// @inheritdoc ISpoke
    172 14×
      function updateReserveConfig(
    173
        uint256 reserveId,
    174
        ReserveConfig calldata config
    175
      ) external restricted {
    176
        Reserve storage reserve = _getReserve(reserveId);
    177
        _validateReserveConfig(config);
    178 28×
        reserve.collateralRisk = config.collateralRisk;
    179 24×
        reserve.flags = ReserveFlagsMap.create({
    180 10×
          initPaused: config.paused,
    181 10×
          initFrozen: config.frozen,
    182 10×
          initBorrowable: config.borrowable,
    183 10×
          initLiquidatable: config.liquidatable,
    184
          initReceiveSharesEnabled: config.receiveSharesEnabled
    185
        });
    186 18×
        emit UpdateReserveConfig(reserveId, config);
    187
      }
    188
    189
      /// @inheritdoc ISpoke
    190 11×
      function updateReservePriceSource(uint256 reserveId, address priceSource) external restricted {
    191
        require(reserveId < _reserveCount, ReserveNotListed());
    192
        _updateReservePriceSource(reserveId, priceSource);
    193
      }
    194
    195
      /// @inheritdoc ISpoke
    196 17×
      function addDynamicReserveConfig(
    197
        uint256 reserveId,
    198
        DynamicReserveConfig calldata dynamicConfig
    199
      ) external restricted returns (uint24) {
    200
        require(reserveId < _reserveCount, ReserveNotListed());
    201 20×
        uint24 dynamicConfigKey = _reserves[reserveId].dynamicConfigKey;
    202
        require(dynamicConfigKey < MAX_ALLOWED_DYNAMIC_CONFIG_KEY, MaximumDynamicConfigKeyReached());
    203
        _validateDynamicReserveConfig(dynamicConfig);
    204
        dynamicConfigKey = dynamicConfigKey.uncheckedAdd(1).toUint24();
    205 33×
        _reserves[reserveId].dynamicConfigKey = dynamicConfigKey;
    206 25×
        _dynamicConfig[reserveId][dynamicConfigKey] = dynamicConfig;
    207 21×
        emit AddDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicConfig);
    208
        return dynamicConfigKey;
    209
      }
    210
    211
      /// @inheritdoc ISpoke
    212 15×
      function updateDynamicReserveConfig(
    213
        uint256 reserveId,
    214
        uint24 dynamicConfigKey,
    215
        DynamicReserveConfig calldata dynamicConfig
    216
      ) external restricted {
    217
        require(reserveId < _reserveCount, ReserveNotListed());
    218 29×
        _validateUpdateDynamicReserveConfig(_dynamicConfig[reserveId][dynamicConfigKey], dynamicConfig);
    219 34×
        _dynamicConfig[reserveId][dynamicConfigKey] = dynamicConfig;
    220 21×
        emit UpdateDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicConfig);
    221
      }
    222
    223
      /// @inheritdoc ISpoke
    224
      function updatePositionManager(address positionManager, bool active) external restricted {
    225 0
        _positionManager[positionManager].active = active;
    226
        emit UpdatePositionManager(positionManager, active);
    227
      }
    228
    229
      /// @inheritdoc ISpokeBase
    230 12×
      function supply(
    231
        uint256 reserveId,
    232
        uint256 amount,
    233
        address onBehalfOf
    234
      ) external onlyPositionManager(onBehalfOf) returns (uint256, uint256) {
    235
        Reserve storage reserve = _getReserve(reserveId);
    236 24×
        UserPosition storage userPosition = _userPositions[onBehalfOf][reserveId];
    237 15×
        _validateSupply(reserve.flags);
    238
    239 19×
        IERC20(reserve.underlying).safeTransferFrom(msg.sender, address(reserve.hub), amount);
    240 75×
        uint256 suppliedShares = reserve.hub.add(reserve.assetId, amount);
    241 38×
        userPosition.suppliedShares += suppliedShares.toUint120();
    242
    243 22×
        emit Supply(reserveId, msg.sender, onBehalfOf, suppliedShares, amount);
    244
    245
        return (suppliedShares, amount);
    246
      }
    247
    248
      /// @inheritdoc ISpokeBase
    249 18×
      function withdraw(
    250
        uint256 reserveId,
    251
        uint256 amount,
    252
        address onBehalfOf
    253
      ) external onlyPositionManager(onBehalfOf) returns (uint256, uint256) {
    254
        Reserve storage reserve = _getReserve(reserveId);
    255 24×
        UserPosition storage userPosition = _userPositions[onBehalfOf][reserveId];
    256 15×
        _validateWithdraw(reserve.flags);
    257 12×
        IHubBase hub = reserve.hub;
    258
        uint256 assetId = reserve.assetId;
    259
    260
        uint256 withdrawnAmount = MathUtils.min(
    261
          amount,
    262 60×
          hub.previewRemoveByShares(assetId, userPosition.suppliedShares)
    263
        );
    264 69×
        uint256 withdrawnShares = hub.remove(assetId, withdrawnAmount, msg.sender);
    265
    266 44×
        userPosition.suppliedShares -= withdrawnShares.toUint120();
    267
    268 22×
        if (_positionStatus[onBehalfOf].isUsingAsCollateral(reserveId)) {
    269
          uint256 newRiskPremium = _refreshAndValidateUserAccountData(onBehalfOf).riskPremium;
    270
          _notifyRiskPremiumUpdate(onBehalfOf, newRiskPremium);
    271
        }
    272
    273 19×
        emit Withdraw(reserveId, msg.sender, onBehalfOf, withdrawnShares, withdrawnAmount);
    274
    275
        return (withdrawnShares, withdrawnAmount);
    276
      }
    277
    278
      /// @inheritdoc ISpokeBase
    279 12×
      function borrow(
    280
        uint256 reserveId,
    281
        uint256 amount,
    282
        address onBehalfOf
    283
      ) external onlyPositionManager(onBehalfOf) returns (uint256, uint256) {
    284
        Reserve storage reserve = _getReserve(reserveId);
    285 25×
        UserPosition storage userPosition = _userPositions[onBehalfOf][reserveId];
    286
        PositionStatus storage positionStatus = _positionStatus[onBehalfOf];
    287 15×
        _validateBorrow(reserve.flags);
    288 12×
        IHubBase hub = reserve.hub;
    289
    290 68×
        uint256 drawnShares = hub.draw(reserve.assetId, amount, msg.sender);
    291 37×
        userPosition.drawnShares += drawnShares.toUint120();
    292 13×
        if (!positionStatus.isBorrowing(reserveId)) {
    293
          positionStatus.setBorrowing(reserveId, true);
    294
        }
    295
    296
        uint256 newRiskPremium = _refreshAndValidateUserAccountData(onBehalfOf).riskPremium;
    297
        _notifyRiskPremiumUpdate(onBehalfOf, newRiskPremium);
    298
    299 19×
        emit Borrow(reserveId, msg.sender, onBehalfOf, drawnShares, amount);
    300
    301
        return (drawnShares, amount);
    302
      }
    303
    304
      /// @inheritdoc ISpokeBase
    305 14×
      function repay(
    306
        uint256 reserveId,
    307
        uint256 amount,
    308
        address onBehalfOf
    309
      ) external onlyPositionManager(onBehalfOf) returns (uint256, uint256) {
    310
        Reserve storage reserve = _getReserve(reserveId);
    311 24×
        UserPosition storage userPosition = _userPositions[onBehalfOf][reserveId];
    312 15×
        _validateRepay(reserve.flags);
    313
    314 67×
        uint256 drawnIndex = reserve.hub.getAssetDrawnIndex(reserve.assetId);
    315 11×
        (uint256 drawnDebtRestored, uint256 premiumDebtRayRestored) = userPosition
    316
          .calculateRestoreAmount(drawnIndex, amount);
    317 10×
        uint256 restoredShares = drawnDebtRestored.rayDivDown(drawnIndex);
    318
    319 11×
        IHubBase.PremiumDelta memory premiumDelta = userPosition.getPremiumDelta({
    320
          drawnSharesTaken: restoredShares,
    321
          drawnIndex: drawnIndex,
    322 16×
          riskPremium: _positionStatus[onBehalfOf].riskPremium,
    323
          restoredPremiumRay: premiumDebtRayRestored
    324
        });
    325
    326 15×
        uint256 totalDebtRestored = drawnDebtRestored + premiumDebtRayRestored.fromRayUp();
    327 11×
        IERC20(reserve.underlying).safeTransferFrom(
    328
          msg.sender,
    329
          address(reserve.hub),
    330
          totalDebtRestored
    331
        );
    332 81×
        reserve.hub.restore(reserve.assetId, drawnDebtRestored, premiumDelta);
    333
    334
        userPosition.applyPremiumDelta(premiumDelta);
    335 41×
        userPosition.drawnShares -= restoredShares.toUint120();
    336
        if (userPosition.drawnShares == 0) {
    337 19×
          PositionStatus storage positionStatus = _positionStatus[onBehalfOf];
    338 13×
          positionStatus.setBorrowing(reserveId, false);
    339
        }
    340
    341 26×
        emit Repay(reserveId, msg.sender, onBehalfOf, restoredShares, totalDebtRestored, premiumDelta);
    342
    343
        return (restoredShares, totalDebtRestored);
    344
      }
    345
    346
      /// @inheritdoc ISpokeBase
    347 17×
      function liquidationCall(
    348
        uint256 collateralReserveId,
    349
        uint256 debtReserveId,
    350
        address user,
    351
        uint256 debtToCover,
    352
        bool receiveShares
    353 10×
      ) external {
    354
        Reserve storage collateralReserve = _getReserve(collateralReserveId);
    355
        Reserve storage debtReserve = _getReserve(debtReserveId);
    356 20×
        DynamicReserveConfig storage collateralDynConfig = _dynamicConfig[collateralReserveId][
    357 26×
          _userPositions[user][collateralReserveId].dynamicConfigKey
    358
        ];
    359
        UserAccountData memory userAccountData = _calculateUserAccountData(user);
    360
    361 70×
        uint256 drawnIndex = debtReserve.hub.getAssetDrawnIndex(debtReserve.assetId);
    362 35×
        (uint256 drawnDebt, uint256 premiumDebtRay) = _userPositions[user][debtReserveId].getDebt(
    363
          drawnIndex
    364
        );
    365
    366 71×
        LiquidationLogic.LiquidateUserParams memory params = LiquidationLogic.LiquidateUserParams({
    367
          collateralReserveId: collateralReserveId,
    368
          debtReserveId: debtReserveId,
    369
          oracle: ORACLE,
    370
          user: user,
    371
          debtToCover: debtToCover,
    372
          healthFactor: userAccountData.healthFactor,
    373
          drawnDebt: drawnDebt,
    374
          premiumDebtRay: premiumDebtRay,
    375
          drawnIndex: drawnIndex,
    376
          totalDebtValue: userAccountData.totalDebtValue,
    377
          activeCollateralCount: userAccountData.activeCollateralCount,
    378
          borrowedCount: userAccountData.borrowedCount,
    379
          liquidator: msg.sender,
    380
          receiveShares: receiveShares
    381
        });
    382
    383 78×
        bool isUserInDeficit = LiquidationLogic.liquidateUser(
    384
          collateralReserve,
    385
          debtReserve,
    386
          _userPositions,
    387
          _positionStatus,
    388
          _liquidationConfig,
    389
          collateralDynConfig,
    390
          params
    391
        );
    392
    393
        uint256 newRiskPremium = 0;
    394
        if (isUserInDeficit) {
    395
          _reportDeficit(user);
    396
        } else {
    397
          newRiskPremium = _calculateUserAccountData(user).riskPremium;
    398
        }
    399
        _notifyRiskPremiumUpdate(user, newRiskPremium);
    400
      }
    401
    402
      /// @inheritdoc ISpoke
    403 16×
      function setUsingAsCollateral(
    404
        uint256 reserveId,
    405
        bool usingAsCollateral,
    406
        address onBehalfOf
    407
      ) external onlyPositionManager(onBehalfOf) {
    408 17×
        _validateSetUsingAsCollateral(_getReserve(reserveId).flags, usingAsCollateral);
    409 13×
        PositionStatus storage positionStatus = _positionStatus[onBehalfOf];
    410
    411 14×
        if (positionStatus.isUsingAsCollateral(reserveId) == usingAsCollateral) {
    412
          return;
    413
        }
    414
        positionStatus.setUsingAsCollateral(reserveId, usingAsCollateral);
    415
    416
        if (usingAsCollateral) {
    417
          _refreshDynamicConfig(onBehalfOf, reserveId);
    418
        } else {
    419
          uint256 newRiskPremium = _refreshAndValidateUserAccountData(onBehalfOf).riskPremium;
    420
          _notifyRiskPremiumUpdate(onBehalfOf, newRiskPremium);
    421
        }
    422
    423 19×
        emit SetUsingAsCollateral(reserveId, msg.sender, onBehalfOf, usingAsCollateral);
    424
      }
    425
    426
      /// @inheritdoc ISpoke
    427 11×
      function updateUserRiskPremium(address onBehalfOf) external {
    428
        if (!_isPositionManager({user: onBehalfOf, manager: msg.sender})) {
    429
          _checkCanCall(msg.sender, msg.data);
    430
        }
    431
        uint256 newRiskPremium = _calculateUserAccountData(onBehalfOf).riskPremium;
    432
        _notifyRiskPremiumUpdate(onBehalfOf, newRiskPremium);
    433
      }
    434
    435
      /// @inheritdoc ISpoke
    436 11×
      function updateUserDynamicConfig(address onBehalfOf) external {
    437
        if (!_isPositionManager({user: onBehalfOf, manager: msg.sender})) {
    438
          _checkCanCall(msg.sender, msg.data);
    439
        }
    440
        uint256 newRiskPremium = _refreshAndValidateUserAccountData(onBehalfOf).riskPremium;
    441
        _notifyRiskPremiumUpdate(onBehalfOf, newRiskPremium);
    442
      }
    443
    444
      /// @inheritdoc ISpoke
    445 0
      function setUserPositionManager(address positionManager, bool approve) external {
    446 0
        _setUserPositionManager({positionManager: positionManager, user: msg.sender, approve: approve});
    447
      }
    448
    449
      /// @inheritdoc ISpoke
    450 0
      function setUserPositionManagerWithSig(
    451
        address positionManager,
    452
        address user,
    453
        bool approve,
    454
        uint256 nonce,
    455
        uint256 deadline,
    456
        bytes calldata signature
    457 0
      ) external {
    458 0
        require(block.timestamp <= deadline, InvalidSignature());
    459 0
        bytes32 digest = _hashTypedData(
    460 0
          keccak256(
    461 0
            abi.encode(
    462
              SET_USER_POSITION_MANAGER_TYPEHASH,
    463
              positionManager,
    464
              user,
    465
              approve,
    466
              nonce,
    467
              deadline
    468
            )
    469
          )
    470
        );
    471 0
        require(SignatureChecker.isValidSignatureNow(user, digest, signature), InvalidSignature());
    472 0
        _useCheckedNonce(user, nonce);
    473 0
        _setUserPositionManager({positionManager: positionManager, user: user, approve: approve});
    474
      }
    475
    476
      /// @inheritdoc ISpoke
    477 0
      function renouncePositionManagerRole(address onBehalfOf) external {
    478 0
        if (!_positionManager[msg.sender].approval[onBehalfOf]) {
    479
          return;
    480
        }
    481 0
        _positionManager[msg.sender].approval[onBehalfOf] = false;
    482 0
        emit SetUserPositionManager(onBehalfOf, msg.sender, false);
    483
      }
    484
    485
      /// @inheritdoc ISpoke
    486
      function permitReserve(
    487
        uint256 reserveId,
    488
        address onBehalfOf,
    489
        uint256 value,
    490
        uint256 deadline,
    491
        uint8 permitV,
    492
        bytes32 permitR,
    493
        bytes32 permitS
    494 0
      ) external {
    495 0
        Reserve storage reserve = _reserves[reserveId];
    496 0
        address underlying = reserve.underlying;
    497 0
        require(underlying != address(0), ReserveNotListed());
    498 0
        try
    499 0
          IERC20Permit(underlying).permit({
    500
            owner: onBehalfOf,
    501 0
            spender: address(this),
    502
            value: value,
    503
            deadline: deadline,
    504
            v: permitV,
    505
            r: permitR,
    506
            s: permitS
    507
          })
    508
        {} catch {}
    509
      }
    510
    511
      /// @inheritdoc ISpoke
    512 0
      function getLiquidationConfig() external view returns (LiquidationConfig memory) {
    513 0
        return _liquidationConfig;
    514
      }
    515
    516
      /// @inheritdoc ISpoke
    517
      function getReserveCount() external view returns (uint256) {
    518
        return _reserveCount;
    519
      }
    520
    521
      /// @inheritdoc ISpokeBase
    522 0
      function getReserveSuppliedAssets(uint256 reserveId) external view returns (uint256) {
    523 0
        Reserve storage reserve = _getReserve(reserveId);
    524 0
        return reserve.hub.getSpokeAddedAssets(reserve.assetId, address(this));
    525
      }
    526
    527
      /// @inheritdoc ISpokeBase
    528 0
      function getReserveSuppliedShares(uint256 reserveId) external view returns (uint256) {
    529 0
        Reserve storage reserve = _getReserve(reserveId);
    530 0
        return reserve.hub.getSpokeAddedShares(reserve.assetId, address(this));
    531
      }
    532
    533
      /// @inheritdoc ISpokeBase
    534 0
      function getReserveDebt(uint256 reserveId) external view returns (uint256, uint256) {
    535 0
        Reserve storage reserve = _getReserve(reserveId);
    536 0
        return reserve.hub.getSpokeOwed(reserve.assetId, address(this));
    537
      }
    538
    539
      /// @inheritdoc ISpokeBase
    540
      function getReserveTotalDebt(uint256 reserveId) external view returns (uint256) {
    541 0
        Reserve storage reserve = _getReserve(reserveId);
    542
        return reserve.hub.getSpokeTotalOwed(reserve.assetId, address(this));
    543
      }
    544
    545
      /// @inheritdoc ISpoke
    546 21×
      function getReserve(uint256 reserveId) external view returns (Reserve memory) {
    547 79×
        return _getReserve(reserveId);
    548
      }
    549
    550
      /// @inheritdoc ISpoke
    551
      function getReserveConfig(uint256 reserveId) external view returns (ReserveConfig memory) {
    552 0
        Reserve storage reserve = _getReserve(reserveId);
    553 0
        return
    554 0
          ReserveConfig({
    555 0
            collateralRisk: reserve.collateralRisk,
    556 0
            paused: reserve.flags.paused(),
    557
            frozen: reserve.flags.frozen(),
    558
            borrowable: reserve.flags.borrowable(),
    559
            liquidatable: reserve.flags.liquidatable(),
    560
            receiveSharesEnabled: reserve.flags.receiveSharesEnabled()
    561
          });
    562
      }
    563
    564
      /// @inheritdoc ISpoke
    565
      function getDynamicReserveConfig(
    566
        uint256 reserveId,
    567
        uint24 dynamicConfigKey
    568
      ) external view returns (DynamicReserveConfig memory) {
    569 0
        _getReserve(reserveId);
    570 0
        return _dynamicConfig[reserveId][dynamicConfigKey];
    571
      }
    572
    573
      /// @inheritdoc ISpoke
    574 0
      function getUserReserveStatus(
    575
        uint256 reserveId,
    576
        address user
    577 0
      ) external view returns (bool, bool) {
    578 0
        _getReserve(reserveId);
    579 0
        PositionStatus storage positionStatus = _positionStatus[user];
    580 0
        return (positionStatus.isUsingAsCollateral(reserveId), positionStatus.isBorrowing(reserveId));
    581
      }
    582
    583
      /// @inheritdoc ISpokeBase
    584 0
      function getUserSuppliedAssets(uint256 reserveId, address user) external view returns (uint256) {
    585 0
        Reserve storage reserve = _getReserve(reserveId);
    586
        return
    587 0
          reserve.hub.previewRemoveByShares(
    588 0
            reserve.assetId,
    589 0
            _userPositions[user][reserveId].suppliedShares
    590
          );
    591
      }
    592
    593
      /// @inheritdoc ISpokeBase
    594 0
      function getUserSuppliedShares(uint256 reserveId, address user) external view returns (uint256) {
    595 0
        _getReserve(reserveId);
    596 0
        return _userPositions[user][reserveId].suppliedShares;
    597
      }
    598
    599
      /// @inheritdoc ISpokeBase
    600 0
      function getUserDebt(uint256 reserveId, address user) external view returns (uint256, uint256) {
    601 0
        Reserve storage reserve = _getReserve(reserveId);
    602 0
        UserPosition storage userPosition = _userPositions[user][reserveId];
    603 0
        (uint256 drawnDebt, uint256 premiumDebtRay) = userPosition.getDebt(
    604 0
          reserve.hub,
    605 0
          reserve.assetId
    606
        );
    607 0
        return (drawnDebt, premiumDebtRay.fromRayUp());
    608
      }
    609
    610
      /// @inheritdoc ISpokeBase
    611
      function getUserTotalDebt(uint256 reserveId, address user) external view returns (uint256) {
    612 0
        Reserve storage reserve = _getReserve(reserveId);
    613 0
        UserPosition storage userPosition = _userPositions[user][reserveId];
    614 0
        (uint256 drawnDebt, uint256 premiumDebtRay) = userPosition.getDebt(
    615 0
          reserve.hub,
    616 0
          reserve.assetId
    617
        );
    618
        return (drawnDebt + premiumDebtRay.fromRayUp());
    619
      }
    620
    621
      /// @inheritdoc ISpokeBase
    622 0
      function getUserPremiumDebtRay(uint256 reserveId, address user) external view returns (uint256) {
    623 0
        Reserve storage reserve = _getReserve(reserveId);
    624 0
        UserPosition storage userPosition = _userPositions[user][reserveId];
    625 0
        (, uint256 premiumDebtRay) = userPosition.getDebt(reserve.hub, reserve.assetId);
    626
        return premiumDebtRay;
    627
      }
    628
    629
      /// @inheritdoc ISpoke
    630 0
      function getUserPosition(
    631
        uint256 reserveId,
    632
        address user
    633
      ) external view returns (UserPosition memory) {
    634 0
        _getReserve(reserveId);
    635 0
        return _userPositions[user][reserveId];
    636
      }
    637
    638
      /// @inheritdoc ISpoke
    639 0
      function getUserLastRiskPremium(address user) external view returns (uint256) {
    640 0
        return _positionStatus[user].riskPremium;
    641
      }
    642
    643
      /// @inheritdoc ISpoke
    644 21×
      function getUserAccountData(address user) external view returns (UserAccountData memory) {
    645
        // SAFETY: function does not modify state when `refreshConfig` is false.
    646
        return _castToView(_processUserAccountData)(user, false);
    647
      }
    648
    649
      /// @inheritdoc ISpoke
    650
      function getLiquidationBonus(
    651
        uint256 reserveId,
    652
        address user,
    653
        uint256 healthFactor
    654 0
      ) external view returns (uint256) {
    655 0
        _getReserve(reserveId);
    656
        return
    657
          LiquidationLogic.calculateLiquidationBonus({
    658 0
            healthFactorForMaxBonus: _liquidationConfig.healthFactorForMaxBonus,
    659 0
            liquidationBonusFactor: _liquidationConfig.liquidationBonusFactor,
    660 0
            healthFactor: healthFactor,
    661 0
            maxLiquidationBonus: _dynamicConfig[reserveId][
    662 0
              _userPositions[user][reserveId].dynamicConfigKey
    663
            ].maxLiquidationBonus
    664
          });
    665
      }
    666
    667
      /// @inheritdoc ISpoke
    668 0
      function isPositionManagerActive(address positionManager) external view returns (bool) {
    669 0
        return _positionManager[positionManager].active;
    670
      }
    671
    672
      /// @inheritdoc ISpoke
    673 0
      function isPositionManager(address user, address positionManager) external view returns (bool) {
    674 0
        return _isPositionManager(user, positionManager);
    675
      }
    676
    677
      /// @inheritdoc ISpoke
    678 0
      function DOMAIN_SEPARATOR() external view returns (bytes32) {
    679 0
        return _domainSeparator();
    680
      }
    681
    682
      /// @inheritdoc ISpoke
    683 0
      function getLiquidationLogic() external pure returns (address) {
    684 0
        return address(LiquidationLogic);
    685
      }
    686
    687
      function _updateReservePriceSource(uint256 reserveId, address priceSource) internal {
    688
        require(priceSource != address(0), InvalidAddress());
    689 38×
        IAaveOracle(ORACLE).setReserveSource(reserveId, priceSource);
    690 12×
        emit UpdateReservePriceSource(reserveId, priceSource);
    691
      }
    692
    693 0
      function _setUserPositionManager(address positionManager, address user, bool approve) internal {
    694 0
        PositionManagerConfig storage config = _positionManager[positionManager];
    695
        // only allow approval when position manager is active for improved UX
    696 0
        require(!approve || config.active, InactivePositionManager());
    697 0
        config.approval[user] = approve;
    698 0
        emit SetUserPositionManager(user, positionManager, approve);
    699
      }
    700
    701
      /// @notice Calculates and validates the user account data.
    702
      /// @dev It refreshes the dynamic config before calculation.
    703
      /// @dev It checks that the health factor is above the liquidation threshold.
    704
      function _refreshAndValidateUserAccountData(
    705
        address user
    706
      ) internal returns (UserAccountData memory) {
    707
        UserAccountData memory accountData = _processUserAccountData(user, true);
    708 10×
        emit RefreshAllUserDynamicConfig(user);
    709 15×
        require(
    710
          accountData.healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
    711
          HealthFactorBelowThreshold()
    712
        );
    713
        return accountData;
    714
      }
    715
    716
      /// @notice Calculates the user account data with the current user dynamic config.
    717
      function _calculateUserAccountData(address user) internal returns (UserAccountData memory) {
    718
        return _processUserAccountData(user, false); // does not modify state
    719
      }
    720
    721
      /// @notice Process the user account data and updates dynamic config of the user if `refreshConfig` is true.
    722
      function _processUserAccountData(
    723
        address user,
    724
        bool refreshConfig
    725
      ) internal returns (UserAccountData memory accountData) {
    726 15×
        PositionStatus storage positionStatus = _positionStatus[user];
    727
    728
        uint256 reserveId = _reserveCount;
    729
        KeyValueList.List memory collateralInfo = KeyValueList.init(
    730
          positionStatus.collateralCount(reserveId)
    731
        );
    732
        bool borrowing;
    733
        bool collateral;
    734
        while (true) {
    735 10×
          (reserveId, borrowing, collateral) = positionStatus.next(reserveId);
    736
          if (reserveId == PositionStatusMap.NOT_FOUND) break;
    737
    738 26×
          UserPosition storage userPosition = _userPositions[user][reserveId];
    739 10×
          Reserve storage reserve = _reserves[reserveId];
    740
    741 60×
          uint256 assetPrice = IAaveOracle(ORACLE).getReservePrice(reserveId);
    742
          uint256 assetUnit = MathUtils.uncheckedExp(10, reserve.decimals);
    743
    744
          if (collateral) {
    745 29×
            uint256 collateralFactor = _dynamicConfig[reserveId][
    746
              refreshConfig
    747 23×
                ? (userPosition.dynamicConfigKey = reserve.dynamicConfigKey)
    748
                : userPosition.dynamicConfigKey
    749
            ].collateralFactor;
    750
            if (collateralFactor > 0) {
    751
              uint256 suppliedShares = userPosition.suppliedShares;
    752
              if (suppliedShares > 0) {
    753
                // cannot round down to zero
    754 77×
                uint256 userCollateralValue = (reserve.hub.previewRemoveByShares(
    755
                  reserve.assetId,
    756
                  suppliedShares
    757
                ) * assetPrice).wadDivDown(assetUnit);
    758 15×
                accountData.totalCollateralValue += userCollateralValue;
    759
                collateralInfo.add(
    760
                  accountData.activeCollateralCount,
    761
                  reserve.collateralRisk,
    762
                  userCollateralValue
    763
                );
    764 20×
                accountData.avgCollateralFactor += collateralFactor * userCollateralValue;
    765
                accountData.activeCollateralCount = accountData.activeCollateralCount.uncheckedAdd(1);
    766
              }
    767
            }
    768
          }
    769
    770
          if (borrowing) {
    771 15×
            (uint256 drawnDebt, uint256 premiumDebtRay) = userPosition.getDebt(
    772
              reserve.hub,
    773
              reserve.assetId
    774
            );
    775
            // we can simplify since there is no precision loss due to the division here
    776 37×
            accountData.totalDebtValue += ((drawnDebt + premiumDebtRay.fromRayUp()) * assetPrice)
    777
              .wadDivUp(assetUnit);
    778
            accountData.borrowedCount = accountData.borrowedCount.uncheckedAdd(1);
    779
          }
    780
        }
    781
    782 11×
        if (accountData.totalDebtValue > 0) {
    783
          // at this point, `avgCollateralFactor` is the collateral-weighted sum (scaled by `collateralFactor` in BPS)
    784
          // health factor uses this directly for simplicity
    785
          // the division by `totalCollateralValue` to compute the weighted average is done later
    786 21×
          accountData.healthFactor = accountData
    787
            .avgCollateralFactor
    788
            .wadDivDown(accountData.totalDebtValue)
    789
            .fromBpsDown();
    790
        } else {
    791
          accountData.healthFactor = type(uint256).max;
    792
        }
    793
    794
        if (accountData.totalCollateralValue > 0) {
    795 18×
          accountData.avgCollateralFactor = accountData
    796
            .avgCollateralFactor
    797
            .wadDivDown(accountData.totalCollateralValue)
    798
            .fromBpsDown();
    799
        }
    800
    801
        // sort by collateral risk in ASC, collateral value in DESC
    802
        collateralInfo.sortByKey();
    803
    804
        // runs until either the collateral or debt is exhausted
    805
        uint256 debtValueLeftToCover = accountData.totalDebtValue;
    806
    807 12×
        for (uint256 index = 0; index < collateralInfo.length(); ++index) {
    808
          if (debtValueLeftToCover == 0) {
    809
            break;
    810
          }
    811
    812 12×
          (uint256 collateralRisk, uint256 userCollateralValue) = collateralInfo.get(index);
    813
          userCollateralValue = userCollateralValue.min(debtValueLeftToCover);
    814 18×
          accountData.riskPremium += userCollateralValue * collateralRisk;
    815
          debtValueLeftToCover = debtValueLeftToCover.uncheckedSub(userCollateralValue);
    816
        }
    817
    818 10×
        if (debtValueLeftToCover < accountData.totalDebtValue) {
    819 17×
          accountData.riskPremium /= accountData.totalDebtValue.uncheckedSub(debtValueLeftToCover);
    820
        }
    821
    822
        return accountData;
    823
      }
    824
    825
      function _refreshDynamicConfig(address user, uint256 reserveId) internal {
    826 55×
        _userPositions[user][reserveId].dynamicConfigKey = _reserves[reserveId].dynamicConfigKey;
    827
        emit RefreshSingleUserDynamicConfig(user, reserveId);
    828
      }
    829
    830
      /// @notice Refreshes premium for borrowed reserves of `user` with `newRiskPremium`.
    831
      /// @dev Skips the refresh if the user risk premium remains zero.
    832
      function _notifyRiskPremiumUpdate(address user, uint256 newRiskPremium) internal {
    833 12×
        PositionStatus storage positionStatus = _positionStatus[user];
    834 18×
        if (newRiskPremium == 0 && positionStatus.riskPremium == 0) {
    835
          return;
    836
        }
    837 24×
        positionStatus.riskPremium = newRiskPremium.toUint24();
    838
    839
        uint256 reserveId = _reserveCount;
    840 21×
        while ((reserveId = positionStatus.nextBorrowing(reserveId)) != PositionStatusMap.NOT_FOUND) {
    841 29×
          UserPosition storage userPosition = _userPositions[user][reserveId];
    842 10×
          Reserve storage reserve = _reserves[reserveId];
    843
          uint256 assetId = reserve.assetId;
    844
          IHubBase hub = reserve.hub;
    845
    846 12×
          IHubBase.PremiumDelta memory premiumDelta = userPosition.getPremiumDelta({
    847
            drawnSharesTaken: 0,
    848 53×
            drawnIndex: hub.getAssetDrawnIndex(assetId),
    849
            riskPremium: newRiskPremium,
    850
            restoredPremiumRay: 0
    851
          });
    852
    853 55×
          hub.refreshPremium(assetId, premiumDelta);
    854 11×
          userPosition.applyPremiumDelta(premiumDelta);
    855 20×
          emit RefreshPremiumDebt(reserveId, user, premiumDelta);
    856
        }
    857 16×
        emit UpdateUserRiskPremium(user, newRiskPremium);
    858
      }
    859
    860
      /// @notice Reports deficits for all debt reserves of the user, including the reserve being repaid during liquidation.
    861
      /// @dev Deficit validation should already have occurred during liquidation.
    862
      /// @dev It clears the user position, setting drawn debt, premium debt, and risk premium to zero.
    863
      function _reportDeficit(address user) internal {
    864 12×
        PositionStatus storage positionStatus = _positionStatus[user];
    865
    866
        uint256 reserveId = _reserveCount;
    867 25×
        while ((reserveId = positionStatus.nextBorrowing(reserveId)) != PositionStatusMap.NOT_FOUND) {
    868 27×
          UserPosition storage userPosition = _userPositions[user][reserveId];
    869 10×
          Reserve storage reserve = _reserves[reserveId];
    870
          IHubBase hub = reserve.hub;
    871
          uint256 assetId = reserve.assetId;
    872
    873 55×
          uint256 drawnIndex = hub.getAssetDrawnIndex(assetId);
    874 11×
          (uint256 drawnDebtReported, uint256 premiumDebtRay) = userPosition.getDebt(drawnIndex);
    875
          uint256 deficitShares = drawnDebtReported.rayDivDown(drawnIndex);
    876
    877 11×
          IHubBase.PremiumDelta memory premiumDelta = userPosition.getPremiumDelta({
    878
            drawnSharesTaken: deficitShares,
    879
            drawnIndex: drawnIndex,
    880
            riskPremium: 0,
    881
            restoredPremiumRay: premiumDebtRay
    882
          });
    883
    884 65×
          hub.reportDeficit(assetId, drawnDebtReported, premiumDelta);
    885
          userPosition.applyPremiumDelta(premiumDelta);
    886 37×
          userPosition.drawnShares -= deficitShares.toUint120();
    887 13×
          positionStatus.setBorrowing(reserveId, false);
    888
    889 22×
          emit ReportDeficit(reserveId, user, deficitShares, premiumDelta);
    890
        }
    891
      }
    892
    893
      function _getReserve(uint256 reserveId) internal view returns (Reserve storage) {
    894
        Reserve storage reserve = _reserves[reserveId];
    895
        require(address(reserve.hub) != address(0), ReserveNotListed());
    896
        return reserve;
    897
      }
    898
    899
      /// @dev CollateralFactor of historical config keys cannot be 0, which allows liquidations to proceed.
    900
      function _validateUpdateDynamicReserveConfig(
    901
        DynamicReserveConfig storage currentConfig,
    902
        DynamicReserveConfig calldata newConfig
    903
      ) internal view {
    904
        // sufficient check since maxLiquidationBonus is always >= 100_00
    905 10×
        require(currentConfig.maxLiquidationBonus > 0, ConfigKeyUninitialized());
    906 28×
        require(newConfig.collateralFactor > 0, InvalidCollateralFactor());
    907
        _validateDynamicReserveConfig(newConfig);
    908
      }
    909
    910
      function _validateSupply(ReserveFlags flags) internal pure {
    911 16×
        require(!flags.paused(), ReservePaused());
    912 15×
        require(!flags.frozen(), ReserveFrozen());
    913
      }
    914
    915
      function _validateWithdraw(ReserveFlags flags) internal pure {
    916 16×
        require(!flags.paused(), ReservePaused());
    917
      }
    918
    919
      function _validateBorrow(ReserveFlags flags) internal pure {
    920 16×
        require(!flags.paused(), ReservePaused());
    921 16×
        require(!flags.frozen(), ReserveFrozen());
    922 15×
        require(flags.borrowable(), ReserveNotBorrowable());
    923
        // health factor is checked at the end of borrow action
    924
      }
    925
    926
      function _validateRepay(ReserveFlags flags) internal pure {
    927
        require(!flags.paused(), ReservePaused());
    928
      }
    929
    930
      function _validateSetUsingAsCollateral(ReserveFlags flags, bool usingAsCollateral) internal pure {
    931 16×
        require(!flags.paused(), ReservePaused());
    932
        // can disable as collateral if the reserve is frozen
    933 21×
        require(!usingAsCollateral || !flags.frozen(), ReserveFrozen());
    934
      }
    935
    936
      /// @notice Returns whether `manager` is active & approved positionManager for `user`.
    937
      function _isPositionManager(address user, address manager) internal view returns (bool) {
    938 10×
        if (user == manager) return true;
    939 0
        PositionManagerConfig storage config = _positionManager[manager];
    940 0
        return config.active && config.approval[user];
    941
      }
    942
    943
      function _validateReserveConfig(ReserveConfig calldata config) internal pure {
    944 27×
        require(config.collateralRisk <= MAX_ALLOWED_COLLATERAL_RISK, InvalidCollateralRisk());
    945
      }
    946
    947
      /// @dev Enforces compatible `maxLiquidationBonus` and `collateralFactor` so at the moment debt is created
    948
      /// there is enough collateral to cover liquidation.
    949
      function _validateDynamicReserveConfig(DynamicReserveConfig calldata config) internal pure {
    950 16×
        require(
    951 21×
          config.collateralFactor < PercentageMath.PERCENTAGE_FACTOR &&
    952 14×
            config.maxLiquidationBonus >= PercentageMath.PERCENTAGE_FACTOR &&
    953 32×
            config.maxLiquidationBonus.percentMulUp(config.collateralFactor) <
    954
            PercentageMath.PERCENTAGE_FACTOR,
    955
          InvalidCollateralFactorAndMaxLiquidationBonus()
    956
        );
    957 29×
        require(config.liquidationFee <= PercentageMath.PERCENTAGE_FACTOR, InvalidLiquidationFee());
    958
      }
    959
    960
      function _domainNameAndVersion() internal pure override returns (string memory, string memory) {
    961 35×
        return ('Spoke', '1');
    962
      }
    963
    964
      function _castToView(
    965
        function(address, bool) internal returns (UserAccountData memory) fnIn
    966
      )
    967
        internal
    968
        pure
    969
        returns (function(address, bool) internal view returns (UserAccountData memory) fnOut)
    970
      {
    971
        assembly ('memory-safe') {
    972
          fnOut := fnIn
    973
        }
    974
      }
    975
    }
    0% src/spoke/SpokeConfigurator.sol
    Lines covered: 0 / 128 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    6
    import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
    7
    import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    8
    import {ISpokeConfigurator} from 'src/spoke/interfaces/ISpokeConfigurator.sol';
    9
    10
    /// @title SpokeConfigurator
    11
    /// @author Aave Labs
    12
    /// @notice Handles administrative functions on the spoke.
    13
    /// @dev Must be granted permission by the spoke.
    14 0
    contract SpokeConfigurator is Ownable2Step, ISpokeConfigurator {
    15
      using SafeCast for uint256;
    16
    17
      mapping(address spoke => uint256) internal _maxReserves;
    18
    19
      /// @dev Constructor.
    20
      /// @param owner_ The address of the owner.
    21 0
      constructor(address owner_) Ownable(owner_) {}
    22
    23
      /// @inheritdoc ISpokeConfigurator
    24 0
      function updateReservePriceSource(
    25
        address spoke,
    26
        uint256 reserveId,
    27
        address priceSource
    28
      ) external onlyOwner {
    29 0
        ISpoke(spoke).updateReservePriceSource(reserveId, priceSource);
    30
      }
    31
    32
      /// @inheritdoc ISpokeConfigurator
    33 0
      function updateLiquidationTargetHealthFactor(
    34
        address spoke,
    35
        uint256 targetHealthFactor
    36
      ) external onlyOwner {
    37 0
        ISpoke targetSpoke = ISpoke(spoke);
    38 0
        ISpoke.LiquidationConfig memory liquidationConfig = targetSpoke.getLiquidationConfig();
    39 0
        liquidationConfig.targetHealthFactor = targetHealthFactor.toUint128();
    40 0
        targetSpoke.updateLiquidationConfig(liquidationConfig);
    41
      }
    42
    43
      /// @inheritdoc ISpokeConfigurator
    44 0
      function updateHealthFactorForMaxBonus(
    45
        address spoke,
    46
        uint256 healthFactorForMaxBonus
    47
      ) external onlyOwner {
    48 0
        ISpoke targetSpoke = ISpoke(spoke);
    49 0
        ISpoke.LiquidationConfig memory liquidationConfig = targetSpoke.getLiquidationConfig();
    50 0
        liquidationConfig.healthFactorForMaxBonus = healthFactorForMaxBonus.toUint64();
    51 0
        targetSpoke.updateLiquidationConfig(liquidationConfig);
    52
      }
    53
    54
      /// @inheritdoc ISpokeConfigurator
    55 0
      function updateLiquidationBonusFactor(
    56
        address spoke,
    57
        uint256 liquidationBonusFactor
    58 0
      ) external onlyOwner {
    59 0
        ISpoke targetSpoke = ISpoke(spoke);
    60 0
        ISpoke.LiquidationConfig memory liquidationConfig = targetSpoke.getLiquidationConfig();
    61 0
        liquidationConfig.liquidationBonusFactor = liquidationBonusFactor.toUint16();
    62 0
        targetSpoke.updateLiquidationConfig(liquidationConfig);
    63
      }
    64
    65
      /// @inheritdoc ISpokeConfigurator
    66 0
      function updateLiquidationConfig(
    67
        address spoke,
    68
        ISpoke.LiquidationConfig calldata liquidationConfig
    69
      ) external onlyOwner {
    70 0
        ISpoke(spoke).updateLiquidationConfig(liquidationConfig);
    71
      }
    72
    73
      /// @inheritdoc ISpokeConfigurator
    74 0
      function updateMaxReserves(address spoke, uint256 maxReserves) external onlyOwner {
    75 0
        _maxReserves[spoke] = maxReserves;
    76 0
        emit UpdateMaxReserves(spoke, maxReserves);
    77
      }
    78
    79
      /// @inheritdoc ISpokeConfigurator
    80 0
      function addReserve(
    81
        address spoke,
    82
        address hub,
    83
        uint256 assetId,
    84
        address priceSource,
    85
        ISpoke.ReserveConfig calldata config,
    86
        ISpoke.DynamicReserveConfig calldata dynamicConfig
    87 0
      ) external onlyOwner returns (uint256) {
    88 0
        require(
    89 0
          ISpoke(spoke).getReserveCount() < _maxReserves[spoke],
    90 0
          MaximumReservesReached(spoke, _maxReserves[spoke])
    91
        );
    92 0
        return ISpoke(spoke).addReserve(hub, assetId, priceSource, config, dynamicConfig);
    93
      }
    94
    95
      /// @inheritdoc ISpokeConfigurator
    96 0
      function updatePaused(address spoke, uint256 reserveId, bool paused) external onlyOwner {
    97 0
        ISpoke targetSpoke = ISpoke(spoke);
    98 0
        ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    99 0
        reserveConfig.paused = paused;
    100 0
        targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    101
      }
    102
    103
      /// @inheritdoc ISpokeConfigurator
    104 0
      function updateFrozen(address spoke, uint256 reserveId, bool frozen) external onlyOwner {
    105 0
        ISpoke targetSpoke = ISpoke(spoke);
    106 0
        ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    107 0
        reserveConfig.frozen = frozen;
    108 0
        targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    109
      }
    110
    111
      /// @inheritdoc ISpokeConfigurator
    112 0
      function updateBorrowable(address spoke, uint256 reserveId, bool borrowable) external onlyOwner {
    113 0
        ISpoke targetSpoke = ISpoke(spoke);
    114 0
        ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    115 0
        reserveConfig.borrowable = borrowable;
    116 0
        targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    117
      }
    118
    119
      /// @inheritdoc ISpokeConfigurator
    120 0
      function updateLiquidatable(
    121
        address spoke,
    122
        uint256 reserveId,
    123
        bool liquidatable
    124 0
      ) external onlyOwner {
    125 0
        ISpoke targetSpoke = ISpoke(spoke);
    126 0
        ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    127 0
        reserveConfig.liquidatable = liquidatable;
    128 0
        targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    129
      }
    130
    131
      /// @inheritdoc ISpokeConfigurator
    132 0
      function updateReceiveSharesEnabled(
    133
        address spoke,
    134
        uint256 reserveId,
    135
        bool receiveSharesEnabled
    136
      ) external onlyOwner {
    137 0
        ISpoke targetSpoke = ISpoke(spoke);
    138 0
        ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    139 0
        reserveConfig.receiveSharesEnabled = receiveSharesEnabled;
    140 0
        targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    141
      }
    142
    143
      /// @inheritdoc ISpokeConfigurator
    144 0
      function updateCollateralRisk(
    145
        address spoke,
    146
        uint256 reserveId,
    147
        uint256 collateralRisk
    148
      ) external onlyOwner {
    149 0
        ISpoke targetSpoke = ISpoke(spoke);
    150 0
        ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    151 0
        reserveConfig.collateralRisk = collateralRisk.toUint24();
    152 0
        targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    153
      }
    154
    155
      /// @inheritdoc ISpokeConfigurator
    156 0
      function addCollateralFactor(
    157
        address spoke,
    158
        uint256 reserveId,
    159
        uint16 collateralFactor
    160 0
      ) external onlyOwner returns (uint24) {
    161 0
        ISpoke targetSpoke = ISpoke(spoke);
    162 0
        ISpoke.DynamicReserveConfig memory dynamicReserveConfig = targetSpoke.getDynamicReserveConfig(
    163 0
          reserveId,
    164 0
          _getReserveLastDynamicConfigKey(spoke, reserveId)
    165
        );
    166 0
        dynamicReserveConfig.collateralFactor = collateralFactor;
    167 0
        return targetSpoke.addDynamicReserveConfig(reserveId, dynamicReserveConfig);
    168
      }
    169
    170
      /// @inheritdoc ISpokeConfigurator
    171 0
      function updateCollateralFactor(
    172
        address spoke,
    173
        uint256 reserveId,
    174
        uint24 dynamicConfigKey,
    175
        uint16 collateralFactor
    176 0
      ) external onlyOwner {
    177 0
        ISpoke targetSpoke = ISpoke(spoke);
    178 0
        ISpoke.DynamicReserveConfig memory dynamicReserveConfig = targetSpoke.getDynamicReserveConfig(
    179
          reserveId,
    180
          dynamicConfigKey
    181
        );
    182 0
        dynamicReserveConfig.collateralFactor = collateralFactor;
    183 0
        targetSpoke.updateDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicReserveConfig);
    184
      }
    185
    186
      /// @inheritdoc ISpokeConfigurator
    187 0
      function addMaxLiquidationBonus(
    188
        address spoke,
    189
        uint256 reserveId,
    190
        uint256 maxLiquidationBonus
    191 0
      ) external onlyOwner returns (uint24) {
    192 0
        ISpoke targetSpoke = ISpoke(spoke);
    193 0
        ISpoke.DynamicReserveConfig memory dynamicReserveConfig = targetSpoke.getDynamicReserveConfig(
    194 0
          reserveId,
    195 0
          _getReserveLastDynamicConfigKey(spoke, reserveId)
    196
        );
    197 0
        dynamicReserveConfig.maxLiquidationBonus = maxLiquidationBonus.toUint32();
    198 0
        return targetSpoke.addDynamicReserveConfig(reserveId, dynamicReserveConfig);
    199
      }
    200
    201
      /// @inheritdoc ISpokeConfigurator
    202 0
      function updateMaxLiquidationBonus(
    203
        address spoke,
    204
        uint256 reserveId,
    205
        uint24 dynamicConfigKey,
    206
        uint256 maxLiquidationBonus
    207
      ) external onlyOwner {
    208 0
        ISpoke targetSpoke = ISpoke(spoke);
    209 0
        ISpoke.DynamicReserveConfig memory dynamicReserveConfig = targetSpoke.getDynamicReserveConfig(
    210
          reserveId,
    211
          dynamicConfigKey
    212
        );
    213 0
        dynamicReserveConfig.maxLiquidationBonus = maxLiquidationBonus.toUint32();
    214 0
        targetSpoke.updateDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicReserveConfig);
    215
      }
    216
    217
      /// @inheritdoc ISpokeConfigurator
    218 0
      function addLiquidationFee(
    219
        address spoke,
    220
        uint256 reserveId,
    221
        uint256 liquidationFee
    222 0
      ) external onlyOwner returns (uint24) {
    223 0
        ISpoke targetSpoke = ISpoke(spoke);
    224 0
        ISpoke.DynamicReserveConfig memory dynamicReserveConfig = targetSpoke.getDynamicReserveConfig(
    225 0
          reserveId,
    226 0
          _getReserveLastDynamicConfigKey(spoke, reserveId)
    227
        );
    228 0
        dynamicReserveConfig.liquidationFee = liquidationFee.toUint16();
    229 0
        return targetSpoke.addDynamicReserveConfig(reserveId, dynamicReserveConfig);
    230
      }
    231
    232
      /// @inheritdoc ISpokeConfigurator
    233 0
      function updateLiquidationFee(
    234
        address spoke,
    235
        uint256 reserveId,
    236
        uint24 dynamicConfigKey,
    237
        uint256 liquidationFee
    238
      ) external onlyOwner {
    239 0
        ISpoke targetSpoke = ISpoke(spoke);
    240 0
        ISpoke.DynamicReserveConfig memory dynamicReserveConfig = targetSpoke.getDynamicReserveConfig(
    241
          reserveId,
    242
          dynamicConfigKey
    243
        );
    244 0
        dynamicReserveConfig.liquidationFee = liquidationFee.toUint16();
    245 0
        targetSpoke.updateDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicReserveConfig);
    246
      }
    247
    248
      /// @inheritdoc ISpokeConfigurator
    249 0
      function addDynamicReserveConfig(
    250
        address spoke,
    251
        uint256 reserveId,
    252
        ISpoke.DynamicReserveConfig calldata dynamicConfig
    253 0
      ) external onlyOwner returns (uint24) {
    254 0
        return ISpoke(spoke).addDynamicReserveConfig(reserveId, dynamicConfig);
    255
      }
    256
    257
      /// @inheritdoc ISpokeConfigurator
    258 0
      function updateDynamicReserveConfig(
    259
        address spoke,
    260
        uint256 reserveId,
    261
        uint24 dynamicConfigKey,
    262
        ISpoke.DynamicReserveConfig calldata dynamicConfig
    263
      ) external onlyOwner {
    264 0
        ISpoke(spoke).updateDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicConfig);
    265
      }
    266
    267
      /// @inheritdoc ISpokeConfigurator
    268 0
      function pauseAllReserves(address spoke) external onlyOwner {
    269 0
        ISpoke targetSpoke = ISpoke(spoke);
    270 0
        uint256 reserveCount = targetSpoke.getReserveCount();
    271 0
        for (uint256 reserveId = 0; reserveId < reserveCount; ++reserveId) {
    272 0
          ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    273 0
          reserveConfig.paused = true;
    274 0
          targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    275
        }
    276
      }
    277
    278
      /// @inheritdoc ISpokeConfigurator
    279 0
      function freezeAllReserves(address spoke) external onlyOwner {
    280 0
        ISpoke targetSpoke = ISpoke(spoke);
    281 0
        uint256 reserveCount = targetSpoke.getReserveCount();
    282 0
        for (uint256 reserveId = 0; reserveId < reserveCount; ++reserveId) {
    283 0
          ISpoke.ReserveConfig memory reserveConfig = targetSpoke.getReserveConfig(reserveId);
    284 0
          reserveConfig.frozen = true;
    285 0
          targetSpoke.updateReserveConfig(reserveId, reserveConfig);
    286
        }
    287
      }
    288
    289
      /// @inheritdoc ISpokeConfigurator
    290 0
      function updatePositionManager(
    291
        address spoke,
    292
        address positionManager,
    293
        bool active
    294
      ) external onlyOwner {
    295 0
        ISpoke(spoke).updatePositionManager(positionManager, active);
    296
      }
    297
    298
      /// @inheritdoc ISpokeConfigurator
    299 0
      function getMaxReserves(address spoke) external view returns (uint256) {
    300 0
        return _maxReserves[spoke];
    301
      }
    302
    303
      /// @dev Returns the last dynamic config key of the reserve for the specified Spoke.
    304 0
      function _getReserveLastDynamicConfigKey(
    305
        address spoke,
    306
        uint256 reserveId
    307 0
      ) internal view returns (uint24) {
    308 0
        return ISpoke(spoke).getReserve(reserveId).dynamicConfigKey;
    309
      }
    310
    }
    12% src/spoke/TreasurySpoke.sol
    Lines covered: 4 / 32 (12%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
    6
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    7
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    8
    import {IHubBase} from 'src/hub/interfaces/IHubBase.sol';
    9
    import {ITreasurySpoke, ISpokeBase} from 'src/spoke/interfaces/ITreasurySpoke.sol';
    10
    11
    /// @title TreasurySpoke
    12
    /// @author Aave Labs
    13
    /// @notice Spoke contract used as a treasury where accumulated fees are treated as supplied assets.
    14
    /// @dev Dedicated to a single user, controlled exclusively by the owner.
    15
    /// @dev Utilizes all assets from the Hub without restrictions, making reserve and asset identifiers aligned.
    16
    /// @dev Allows withdraw to claim fees and supply to invest back into the Hub via this dedicated spoke.
    17 137×
    contract TreasurySpoke is ITreasurySpoke, Ownable2Step {
    18
      using SafeERC20 for IERC20;
    19
    20
      /// @inheritdoc ITreasurySpoke
    21 0
      IHubBase public immutable HUB;
    22
    23
      /// @dev Constructor.
    24
      /// @param owner_ The address of the owner.
    25
      /// @param hub_ The address of the Hub.
    26 28×
      constructor(address owner_, address hub_) Ownable(owner_) {
    27
        require(hub_ != address(0), InvalidAddress());
    28
    29
        HUB = IHubBase(hub_);
    30
      }
    31
    32
      /// @inheritdoc ITreasurySpoke
    33 0
      function supply(
    34
        uint256 reserveId,
    35
        uint256 amount,
    36
        address
    37 0
      ) external onlyOwner returns (uint256, uint256) {
    38 0
        (address underlying, ) = HUB.getAssetUnderlyingAndDecimals(reserveId);
    39 0
        IERC20(underlying).safeTransferFrom(msg.sender, address(HUB), amount);
    40 0
        uint256 shares = HUB.add(reserveId, amount);
    41
    42 0
        return (shares, amount);
    43
      }
    44
    45
      /// @inheritdoc ITreasurySpoke
    46 0
      function withdraw(
    47
        uint256 reserveId,
    48
        uint256 amount,
    49
        address
    50 0
      ) external onlyOwner returns (uint256, uint256) {
    51
        // if amount to withdraw is greater than total supplied, withdraw all supplied assets
    52 0
        uint256 withdrawnAmount = MathUtils.min(
    53 0
          amount,
    54 0
          HUB.getSpokeAddedAssets(reserveId, address(this))
    55
        );
    56 0
        uint256 withdrawnShares = HUB.remove(reserveId, withdrawnAmount, msg.sender);
    57
    58 0
        return (withdrawnShares, withdrawnAmount);
    59
      }
    60
    61
      /// @inheritdoc ITreasurySpoke
    62 0
      function transfer(address token, address to, uint256 amount) external onlyOwner {
    63 0
        IERC20(token).safeTransfer(to, amount);
    64
      }
    65
    66
      /// @inheritdoc ITreasurySpoke
    67
      function getSuppliedAmount(uint256 reserveId) external view returns (uint256) {
    68
        return HUB.getSpokeAddedAssets(reserveId, address(this));
    69
      }
    70
    71
      /// @inheritdoc ITreasurySpoke
    72
      function getSuppliedShares(uint256 reserveId) external view returns (uint256) {
    73
        return HUB.getSpokeAddedShares(reserveId, address(this));
    74
      }
    75
    76
      /// @inheritdoc ISpokeBase
    77
      function borrow(uint256, uint256, address) external pure returns (uint256, uint256) {
    78
        revert UnsupportedAction();
    79
      }
    80
    81
      /// @inheritdoc ISpokeBase
    82 0
      function repay(uint256, uint256, address) external pure returns (uint256, uint256) {
    83 0
        revert UnsupportedAction();
    84
      }
    85
    86
      /// @inheritdoc ISpokeBase
    87 0
      function liquidationCall(uint256, uint256, address, uint256, bool) external pure {
    88 0
        revert UnsupportedAction();
    89
      }
    90
    91
      /// @inheritdoc ISpokeBase
    92 0
      function getUserDebt(uint256, address) external pure returns (uint256, uint256) {}
    93
    94
      /// @inheritdoc ISpokeBase
    95
      function getUserTotalDebt(uint256, address) external pure returns (uint256) {}
    96
    97
      /// @inheritdoc ISpokeBase
    98
      function getUserPremiumDebtRay(uint256, address) external pure returns (uint256) {}
    99
    100
      /// @inheritdoc ISpokeBase
    101 0
      function getReserveSuppliedAssets(uint256 reserveId) external view returns (uint256) {
    102 0
        return HUB.getSpokeAddedAssets(reserveId, address(this));
    103
      }
    104
    105
      /// @inheritdoc ISpokeBase
    106 0
      function getReserveSuppliedShares(uint256 reserveId) external view returns (uint256) {
    107 0
        return HUB.getSpokeAddedShares(reserveId, address(this));
    108
      }
    109
    110
      /// @inheritdoc ISpokeBase
    111
      function getUserSuppliedAssets(uint256, address) external pure returns (uint256) {}
    112
    113
      /// @inheritdoc ISpokeBase
    114 0
      function getUserSuppliedShares(uint256, address) external pure returns (uint256) {}
    115
    116
      /// @inheritdoc ISpokeBase
    117 0
      function getReserveDebt(uint256) external pure returns (uint256, uint256) {}
    118
    119
      /// @inheritdoc ISpokeBase
    120 0
      function getReserveTotalDebt(uint256) external pure returns (uint256) {}
    121
    }
    20% src/spoke/instances/SpokeInstance.sol
    Lines covered: 2 / 10 (20%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity 0.8.28;
    4
    5
    import {LiquidationLogic} from 'src/spoke/libraries/LiquidationLogic.sol';
    6
    import {Spoke} from 'src/spoke/Spoke.sol';
    7
    8
    /// @title SpokeInstance
    9
    /// @author Aave Labs
    10
    /// @notice Implementation contract for the Spoke.
    11 415×
    contract SpokeInstance is Spoke {
    12 0
      uint64 public constant SPOKE_REVISION = 1;
    13
    14
      /// @dev Constructor.
    15
      /// @dev During upgrade, must ensure that the new oracle is supporting existing assets on the spoke and the replaced oracle.
    16
      /// @param oracle_ The address of the oracle.
    17 44×
      constructor(address oracle_) Spoke(oracle_) {
    18
        // @audit FIXME: Remove _disableInitializers to fix echidna coverage https://github.com/crytic/echidna/issues/1116#issuecomment-3596746122
    19
        // _disableInitializers();
    20
      }
    21
    22
      /// @notice Initializer.
    23
      /// @dev The authority contract must implement the `AccessManaged` interface for access control.
    24
      /// @param authority The address of the authority contract which manages permissions.
    25 0
      function initialize(address authority) external override reinitializer(SPOKE_REVISION) {
    26 0
        emit UpdateOracle(ORACLE);
    27 0
        require(authority != address(0), InvalidAddress());
    28 0
        __AccessManaged_init(authority);
    29 0
        if (_liquidationConfig.targetHealthFactor == 0) {
    30 0
          _liquidationConfig.targetHealthFactor = HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
    31 0
          emit UpdateLiquidationConfig(_liquidationConfig);
    32
        }
    33
      }
    34
    }
    96% src/spoke/libraries/KeyValueList.sol
    Lines covered: 25 / 26 (96%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {Arrays} from 'src/dependencies/openzeppelin/Arrays.sol';
    6
    7
    /// @title KeyValueList Library
    8
    /// @author Aave Labs
    9
    /// @notice Library to pack key-value pairs in a list.
    10
    /// @dev The `sortByKey` helper sorts by ascending order of the `key` & in case of collision by descending order of the `value`.
    11
    /// @dev This is achieved by sorting the packed `key-value` pair in descending order, but storing the invert of the `key` (ie `_MAX_KEY - key`).
    12
    /// @dev Uninitialized keys are returned as (key: 0, value: 0) and are placed at the end of the list after sorting.
    13 0
    library KeyValueList {
    14
      /// @notice Thrown when adding a key which can't be stored in `_KEY_BITS` or value in `_VALUE_BITS`.
    15
      error MaxDataSizeExceeded();
    16
    17
      struct List {
    18
        uint256[] _inner;
    19
      }
    20
    21
      uint256 internal constant _KEY_BITS = 32;
    22
      uint256 internal constant _VALUE_BITS = 224;
    23 15×
      uint256 internal constant _MAX_KEY = (1 << _KEY_BITS) - 1;
    24
      uint256 internal constant _MAX_VALUE = (1 << _VALUE_BITS) - 1;
    25 15×
      uint256 internal constant _KEY_SHIFT = 256 - _KEY_BITS;
    26
    27
      /// @notice Allocates memory for a KeyValue list of `size` elements.
    28
      function init(uint256 size) internal pure returns (List memory) {
    29 50×
        return List(new uint256[](size));
    30
      }
    31
    32
      /// @notice Returns the length of the list.
    33
      function length(List memory self) internal pure returns (uint256) {
    34
        return self._inner.length;
    35
      }
    36
    37
      /// @notice Inserts packed `key`, `value` at `idx`. Reverts if data exceeds maximum allowed size.
    38
      /// @dev Reverts if `key` equals or exceeds the `_MAX_KEY` value and reverts if `value` equals or exceeds the `_MAX_VALUE` value.
    39
      function add(List memory self, uint256 idx, uint256 key, uint256 value) internal pure {
    40 12×
        require(key < _MAX_KEY && value < _MAX_VALUE, MaxDataSizeExceeded());
    41 27×
        self._inner[idx] = pack(key, value);
    42
      }
    43
    44
      /// @notice Returns the key-value pair at the given index.
    45
      /// @dev Uninitialized keys are returned as (key: 0, value: 0).
    46
      function get(List memory self, uint256 idx) internal pure returns (uint256, uint256) {
    47 26×
        return unpack(self._inner[idx]);
    48
      }
    49
    50
      /// @notice Sorts the list in-place by ascending order of `key`, and descending order of `value` on collision.
    51
      /// @dev All uninitialized keys are placed at the end of the list after sorting.
    52
      /// @dev Since `key` is in the MSB, we can sort by the key by sorting the array in descending order
    53
      /// (so the keys are in ascending order when unpacking, due to the inversion when packed).
    54
      function sortByKey(List memory self) internal pure {
    55
        Arrays.sort(self._inner, gtComparator);
    56
      }
    57
    58
      /// @notice Packs a given `key`, `value` pair into a single word.
    59
      /// @dev Bound checks are expected to be done before packing.
    60
      function pack(uint256 key, uint256 value) internal pure returns (uint256) {
    61 12×
        return ((_MAX_KEY - key) << _KEY_SHIFT) | value;
    62
      }
    63
    64
      /// @notice Unpacks `key` from a previously packed word containing `key` and `value`.
    65
      /// @dev The key is stored in the most significant bits of the word.
    66
      function unpackKey(uint256 data) internal pure returns (uint256) {
    67
        return _MAX_KEY - (data >> _KEY_SHIFT);
    68
      }
    69
    70
      /// @notice Unpacks `value` from a previously packed word containing `key` and `value`.
    71
      /// @dev The value is stored in the least significant bits of the word.
    72
      function unpackValue(uint256 data) internal pure returns (uint256) {
    73 14×
        return data & ((1 << _KEY_SHIFT) - 1);
    74
      }
    75
    76
      /// @notice Unpacks both `key` and `value` from a previously packed word containing `key` and `value`.
    77
      /// @dev Uninitialized keys are returned as (key: 0, value: 0).
    78
      /// @param data The packed word containing `key` and `value`.
    79
      function unpack(uint256 data) internal pure returns (uint256, uint256) {
    80 10×
        if (data == 0) return (0, 0);
    81 14×
        return (unpackKey(data), unpackValue(data));
    82
      }
    83
    84
      /// @notice Comparator function performing greater-than comparison.
    85
      /// @return True if `a` is greater than `b`.
    86
      function gtComparator(uint256 a, uint256 b) internal pure returns (bool) {
    87
        return a > b;
    88
      }
    89
    }
    98% src/spoke/libraries/LiquidationLogic.sol
    Lines covered: 218 / 222 (98%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    6
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    7
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    8
    import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
    9
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    10
    import {PositionStatusMap} from 'src/spoke/libraries/PositionStatusMap.sol';
    11
    import {UserPositionDebt} from 'src/spoke/libraries/UserPositionDebt.sol';
    12
    import {ReserveFlags, ReserveFlagsMap} from 'src/spoke/libraries/ReserveFlagsMap.sol';
    13
    import {IHubBase} from 'src/hub/interfaces/IHubBase.sol';
    14
    import {IAaveOracle} from 'src/spoke/interfaces/IAaveOracle.sol';
    15
    import {ISpoke, ISpokeBase} from 'src/spoke/interfaces/ISpoke.sol';
    16
    17
    /// @title LiquidationLogic library
    18
    /// @author Aave Labs
    19
    /// @notice Implements the logic for liquidations.
    20 55×
    library LiquidationLogic {
    21
      using SafeCast for *;
    22
      using SafeERC20 for IERC20;
    23
      using MathUtils for *;
    24
      using PercentageMath for uint256;
    25
      using WadRayMath for uint256;
    26
      using UserPositionDebt for ISpoke.UserPosition;
    27
      using ReserveFlagsMap for ReserveFlags;
    28
      using PositionStatusMap for ISpoke.PositionStatus;
    29
    30
      struct LiquidateUserParams {
    31
        uint256 collateralReserveId;
    32
        uint256 debtReserveId;
    33
        address oracle;
    34
        address user;
    35
        uint256 debtToCover;
    36
        uint256 healthFactor;
    37
        uint256 drawnDebt;
    38
        uint256 premiumDebtRay;
    39
        uint256 drawnIndex;
    40
        uint256 totalDebtValue;
    41
        address liquidator;
    42
        uint256 activeCollateralCount;
    43
        uint256 borrowedCount;
    44
        bool receiveShares;
    45
      }
    46
    47
      struct LiquidateCollateralParams {
    48
        uint256 collateralToLiquidate;
    49
        uint256 collateralToLiquidator;
    50
        address liquidator;
    51
        bool receiveShares;
    52
      }
    53
    54
      struct LiquidateDebtParams {
    55
        uint256 debtReserveId;
    56
        uint256 debtToLiquidate;
    57
        uint256 premiumDebtRay;
    58
        uint256 drawnIndex;
    59
        address liquidator;
    60
      }
    61
    62
      struct ValidateLiquidationCallParams {
    63
        address user;
    64
        address liquidator;
    65
        ReserveFlags collateralReserveFlags;
    66
        ReserveFlags debtReserveFlags;
    67
        uint256 collateralReserveBalance;
    68
        uint256 debtReserveBalance;
    69
        uint256 debtToCover;
    70
        uint256 collateralFactor;
    71
        bool isUsingAsCollateral;
    72
        uint256 healthFactor;
    73
        bool receiveShares;
    74
      }
    75
    76
      struct CalculateDebtToTargetHealthFactorParams {
    77
        uint256 totalDebtValue;
    78
        uint256 debtAssetUnit;
    79
        uint256 debtAssetPrice;
    80
        uint256 collateralFactor;
    81
        uint256 liquidationBonus;
    82
        uint256 healthFactor;
    83
        uint256 targetHealthFactor;
    84
      }
    85
    86
      struct CalculateDebtToLiquidateParams {
    87
        uint256 debtReserveBalance;
    88
        uint256 totalDebtValue;
    89
        uint256 debtAssetUnit;
    90
        uint256 debtAssetPrice;
    91
        uint256 debtToCover;
    92
        uint256 collateralFactor;
    93
        uint256 liquidationBonus;
    94
        uint256 healthFactor;
    95
        uint256 targetHealthFactor;
    96
      }
    97
    98
      struct CalculateLiquidationAmountsParams {
    99
        uint256 collateralReserveBalance;
    100
        uint256 collateralAssetUnit;
    101
        uint256 collateralAssetPrice;
    102
        uint256 debtReserveBalance;
    103
        uint256 totalDebtValue;
    104
        uint256 debtAssetUnit;
    105
        uint256 debtAssetPrice;
    106
        uint256 debtToCover;
    107
        uint256 collateralFactor;
    108
        uint256 healthFactorForMaxBonus;
    109
        uint256 liquidationBonusFactor;
    110
        uint256 maxLiquidationBonus;
    111
        uint256 targetHealthFactor;
    112
        uint256 healthFactor;
    113
        uint256 liquidationFee;
    114
      }
    115
    116
      struct LiquidationAmounts {
    117
        uint256 collateralToLiquidate;
    118
        uint256 collateralToLiquidator;
    119
        uint256 debtToLiquidate;
    120
      }
    121
    122
      // see ISpoke.HEALTH_FACTOR_LIQUIDATION_THRESHOLD docs
    123 15×
      uint64 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
    124
    125
      // see ISpoke.DUST_LIQUIDATION_THRESHOLD docs
    126 0
      uint256 public constant DUST_LIQUIDATION_THRESHOLD = 1000e26;
    127
    128
      /// @notice Liquidates a user position.
    129
      /// @param collateralReserve The collateral reserve to seize during liquidation.
    130
      /// @param debtReserve The debt reserve to repay during liquidation.
    131
      /// @param positions The mapping of positions per reserve per user.
    132
      /// @param positionStatus The mapping of position status per user.
    133
      /// @param liquidationConfig The liquidation config.
    134
      /// @param collateralDynConfig The collateral dynamic config.
    135
      /// @param params The liquidate user params.
    136
      /// @return True if the liquidation results in deficit.
    137 31×
      function liquidateUser(
    138
        ISpoke.Reserve storage collateralReserve,
    139
        ISpoke.Reserve storage debtReserve,
    140
        mapping(address user => mapping(uint256 reserveId => ISpoke.UserPosition)) storage positions,
    141
        mapping(address user => ISpoke.PositionStatus) storage positionStatus,
    142
        ISpoke.LiquidationConfig storage liquidationConfig,
    143
        ISpoke.DynamicReserveConfig storage collateralDynConfig,
    144
        LiquidateUserParams memory params
    145
      ) external returns (bool) {
    146 61×
        uint256 collateralReserveBalance = collateralReserve.hub.previewRemoveByShares(
    147
          collateralReserve.assetId,
    148 34×
          positions[params.user][params.collateralReserveId].suppliedShares
    149
        );
    150
        _validateLiquidationCall(
    151 70×
          ValidateLiquidationCallParams({
    152
            user: params.user,
    153
            liquidator: params.liquidator,
    154 13×
            collateralReserveFlags: collateralReserve.flags,
    155 13×
            debtReserveFlags: debtReserve.flags,
    156
            collateralReserveBalance: collateralReserveBalance,
    157 18×
            debtReserveBalance: params.drawnDebt + params.premiumDebtRay.fromRayUp(),
    158
            debtToCover: params.debtToCover,
    159
            collateralFactor: collateralDynConfig.collateralFactor,
    160 21×
            isUsingAsCollateral: positionStatus[params.user].isUsingAsCollateral(
    161
              params.collateralReserveId
    162
            ),
    163
            healthFactor: params.healthFactor,
    164
            receiveShares: params.receiveShares
    165
          })
    166
        );
    167
    168
        LiquidationAmounts memory liquidationAmounts = _calculateLiquidationAmounts(
    169 79×
          CalculateLiquidationAmountsParams({
    170
            collateralReserveBalance: collateralReserveBalance,
    171
            collateralAssetUnit: MathUtils.uncheckedExp(10, collateralReserve.decimals),
    172 64×
            collateralAssetPrice: IAaveOracle(params.oracle).getReservePrice(
    173
              params.collateralReserveId
    174
            ),
    175 18×
            debtReserveBalance: params.drawnDebt + params.premiumDebtRay.fromRayUp(),
    176
            totalDebtValue: params.totalDebtValue,
    177
            debtAssetUnit: MathUtils.uncheckedExp(10, debtReserve.decimals),
    178 68×
            debtAssetPrice: IAaveOracle(params.oracle).getReservePrice(params.debtReserveId),
    179
            debtToCover: params.debtToCover,
    180
            collateralFactor: collateralDynConfig.collateralFactor,
    181
            healthFactorForMaxBonus: liquidationConfig.healthFactorForMaxBonus,
    182
            liquidationBonusFactor: liquidationConfig.liquidationBonusFactor,
    183
            maxLiquidationBonus: collateralDynConfig.maxLiquidationBonus,
    184
            targetHealthFactor: liquidationConfig.targetHealthFactor,
    185
            healthFactor: params.healthFactor,
    186
            liquidationFee: collateralDynConfig.liquidationFee
    187
          })
    188
        );
    189
    190
        (
    191
          uint256 collateralSharesToLiquidate,
    192
          uint256 collateralSharesToLiquidator,
    193
          bool isCollateralPositionEmpty
    194
        ) = _liquidateCollateral(
    195
            collateralReserve,
    196 35×
            positions[params.user][params.collateralReserveId],
    197 35×
            positions[params.liquidator][params.collateralReserveId],
    198 26×
            LiquidateCollateralParams({
    199
              collateralToLiquidate: liquidationAmounts.collateralToLiquidate,
    200
              collateralToLiquidator: liquidationAmounts.collateralToLiquidator,
    201
              liquidator: params.liquidator,
    202
              receiveShares: params.receiveShares
    203
            })
    204
          );
    205
    206
        (
    207
          uint256 drawnSharesToLiquidate,
    208
          IHubBase.PremiumDelta memory premiumDelta,
    209
          bool isDebtPositionEmpty
    210
        ) = _liquidateDebt(
    211
            debtReserve,
    212 35×
            positions[params.user][params.debtReserveId],
    213 19×
            positionStatus[params.user],
    214 28×
            LiquidateDebtParams({
    215
              debtReserveId: params.debtReserveId,
    216
              debtToLiquidate: liquidationAmounts.debtToLiquidate,
    217
              premiumDebtRay: params.premiumDebtRay,
    218
              drawnIndex: params.drawnIndex,
    219
              liquidator: params.liquidator
    220
            })
    221
          );
    222
    223 24×
        emit ISpokeBase.LiquidationCall(
    224
          params.collateralReserveId,
    225
          params.debtReserveId,
    226
          params.user,
    227
          params.liquidator,
    228
          params.receiveShares,
    229
          liquidationAmounts.debtToLiquidate,
    230
          drawnSharesToLiquidate,
    231
          premiumDelta,
    232
          liquidationAmounts.collateralToLiquidate,
    233
          collateralSharesToLiquidate,
    234
          collateralSharesToLiquidator
    235
        );
    236
    237 10×
        return
    238
          _evaluateDeficit({
    239
            isCollateralPositionEmpty: isCollateralPositionEmpty,
    240
            isDebtPositionEmpty: isDebtPositionEmpty,
    241
            activeCollateralCount: params.activeCollateralCount,
    242
            borrowedCount: params.borrowedCount
    243
          });
    244
      }
    245
    246
      /// @notice Calculates the liquidation bonus at a given health factor.
    247
      /// @dev Liquidation Bonus is expressed as a BPS value greater than `PercentageMath.PERCENTAGE_FACTOR`.
    248
      /// @param healthFactorForMaxBonus The health factor for max bonus.
    249
      /// @param liquidationBonusFactor The liquidation bonus factor.
    250
      /// @param healthFactor The health factor.
    251
      /// @param maxLiquidationBonus The max liquidation bonus.
    252
      /// @return The liquidation bonus.
    253
      function calculateLiquidationBonus(
    254
        uint256 healthFactorForMaxBonus,
    255
        uint256 liquidationBonusFactor,
    256
        uint256 healthFactor,
    257
        uint256 maxLiquidationBonus
    258
      ) internal pure returns (uint256) {
    259
        if (healthFactor <= healthFactorForMaxBonus) {
    260
          return maxLiquidationBonus;
    261
        }
    262
    263 18×
        uint256 minLiquidationBonus = (maxLiquidationBonus - PercentageMath.PERCENTAGE_FACTOR)
    264
          .percentMulDown(liquidationBonusFactor) + PercentageMath.PERCENTAGE_FACTOR;
    265
    266
        // linear interpolation between min and max
    267
        // denominator cannot be zero as healthFactorForMaxBonus is always < HEALTH_FACTOR_LIQUIDATION_THRESHOLD
    268
        return
    269
          minLiquidationBonus +
    270
          (maxLiquidationBonus - minLiquidationBonus).mulDivDown(
    271
            HEALTH_FACTOR_LIQUIDATION_THRESHOLD - healthFactor,
    272
            HEALTH_FACTOR_LIQUIDATION_THRESHOLD - healthFactorForMaxBonus
    273
          );
    274
      }
    275
    276
      /// @dev Invoked by `liquidateUser` method.
    277
      /// @return The total amount of collateral shares to be liquidated.
    278
      /// @return The amount of collateral shares that the liquidator receives.
    279
      /// @return True if the user collateral position becomes empty after removing.
    280 11×
      function _liquidateCollateral(
    281
        ISpoke.Reserve storage collateralReserve,
    282
        ISpoke.UserPosition storage collateralPosition,
    283
        ISpoke.UserPosition storage liquidatorCollateralPosition,
    284
        LiquidateCollateralParams memory params
    285
      ) internal returns (uint256, uint256, bool) {
    286 10×
        IHubBase hub = collateralReserve.hub;
    287
        uint256 assetId = collateralReserve.assetId;
    288
    289 57×
        uint256 sharesToLiquidate = hub.previewRemoveByAssets(assetId, params.collateralToLiquidate);
    290 19×
        uint120 userSuppliedShares = collateralPosition.suppliedShares - sharesToLiquidate.toUint120();
    291
    292
        uint256 sharesToLiquidator;
    293
        if (params.collateralToLiquidator > 0) {
    294 11×
          if (params.receiveShares) {
    295 65×
            sharesToLiquidator = hub.previewAddByAssets(assetId, params.collateralToLiquidator);
    296
            if (sharesToLiquidator > 0) {
    297 38×
              liquidatorCollateralPosition.suppliedShares += sharesToLiquidator.toUint120();
    298
            }
    299
          } else {
    300 75×
            sharesToLiquidator = hub.remove(assetId, params.collateralToLiquidator, params.liquidator);
    301
          }
    302
        }
    303
    304 11×
        collateralPosition.suppliedShares = userSuppliedShares;
    305
    306
        if (sharesToLiquidate > sharesToLiquidator) {
    307 53×
          hub.payFeeShares(assetId, sharesToLiquidate.uncheckedSub(sharesToLiquidator));
    308
        }
    309
    310
        return (sharesToLiquidate, sharesToLiquidator, userSuppliedShares == 0);
    311
      }
    312
    313
      /// @dev Invoked by `liquidateUser` method.
    314
      /// @return The amount of drawn shares to be liquidated.
    315
      /// @return A struct representing the changes to premium debt after liquidation.
    316
      /// @return True if the debt position becomes zero after restoring.
    317
      function _liquidateDebt(
    318
        ISpoke.Reserve storage debtReserve,
    319
        ISpoke.UserPosition storage debtPosition,
    320
        ISpoke.PositionStatus storage positionStatus,
    321
        LiquidateDebtParams memory params
    322
      ) internal returns (uint256, IHubBase.PremiumDelta memory, bool) {
    323 17×
        uint256 premiumDebtToLiquidateRay = params.debtToLiquidate.toRay().min(params.premiumDebtRay);
    324 18×
        uint256 drawnDebtLiquidated = params.debtToLiquidate - premiumDebtToLiquidateRay.fromRayUp();
    325 19×
        uint256 drawnSharesLiquidated = drawnDebtLiquidated.rayDivDown(params.drawnIndex);
    326
    327 11×
        IHubBase.PremiumDelta memory premiumDelta = debtPosition.getPremiumDelta({
    328
          drawnSharesTaken: drawnSharesLiquidated,
    329
          drawnIndex: params.drawnIndex,
    330
          riskPremium: positionStatus.riskPremium,
    331
          restoredPremiumRay: premiumDebtToLiquidateRay
    332
        });
    333
    334 11×
        IERC20(debtReserve.underlying).safeTransferFrom(
    335
          params.liquidator,
    336
          address(debtReserve.hub),
    337
          params.debtToLiquidate
    338
        );
    339 81×
        debtReserve.hub.restore(debtReserve.assetId, drawnDebtLiquidated, premiumDelta);
    340
    341
        debtPosition.applyPremiumDelta(premiumDelta);
    342 41×
        debtPosition.drawnShares -= drawnSharesLiquidated.toUint120();
    343
        if (debtPosition.drawnShares == 0) {
    344 0
          positionStatus.setBorrowing(params.debtReserveId, false);
    345 0
          return (drawnSharesLiquidated, premiumDelta, true);
    346
        }
    347
    348
        return (drawnSharesLiquidated, premiumDelta, false);
    349
      }
    350
    351
      /// @notice Validates the liquidation call.
    352
      /// @param params The validate liquidation call params.
    353
      function _validateLiquidationCall(ValidateLiquidationCallParams memory params) internal pure {
    354 27×
        require(params.user != params.liquidator, ISpoke.SelfLiquidation());
    355 22×
        require(params.debtToCover > 0, ISpoke.InvalidDebtToCover());
    356 16×
        require(
    357 11×
          !params.collateralReserveFlags.paused() && !params.debtReserveFlags.paused(),
    358
          ISpoke.ReservePaused()
    359
        );
    360 22×
        require(params.collateralReserveBalance > 0, ISpoke.ReserveNotSupplied());
    361 22×
        require(params.debtReserveBalance > 0, ISpoke.ReserveNotBorrowed());
    362 20×
        require(params.collateralReserveFlags.liquidatable(), ISpoke.CollateralCannotBeLiquidated());
    363 16×
        require(
    364
          params.healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
    365
          ISpoke.HealthFactorNotBelowThreshold()
    366
        );
    367 16×
        require(
    368 16×
          params.collateralFactor > 0 && params.isUsingAsCollateral,
    369
          ISpoke.ReserveNotEnabledAsCollateral()
    370
        );
    371
        if (params.receiveShares) {
    372 16×
          require(
    373
            !params.collateralReserveFlags.frozen() &&
    374
              params.collateralReserveFlags.receiveSharesEnabled(),
    375
            ISpoke.CannotReceiveShares()
    376
          );
    377
        }
    378
      }
    379
    380
      /// @notice Calculates the liquidation amounts.
    381
      /// @dev Invoked by `liquidateUser` method.
    382
      function _calculateLiquidationAmounts(
    383
        CalculateLiquidationAmountsParams memory params
    384
      ) internal pure returns (LiquidationAmounts memory) {
    385
        uint256 liquidationBonus = calculateLiquidationBonus({
    386
          healthFactorForMaxBonus: params.healthFactorForMaxBonus,
    387
          liquidationBonusFactor: params.liquidationBonusFactor,
    388
          healthFactor: params.healthFactor,
    389
          maxLiquidationBonus: params.maxLiquidationBonus
    390
        });
    391
    392
        // To prevent accumulation of dust, one of the following conditions is enforced:
    393
        // 1. liquidate all debt
    394
        // 2. liquidate all collateral
    395
        // 3. leave at least `DUST_LIQUIDATION_THRESHOLD` of collateral and debt (in value terms)
    396
        uint256 debtToLiquidate = _calculateDebtToLiquidate(
    397 43×
          CalculateDebtToLiquidateParams({
    398
            debtReserveBalance: params.debtReserveBalance,
    399
            totalDebtValue: params.totalDebtValue,
    400
            debtAssetUnit: params.debtAssetUnit,
    401
            debtAssetPrice: params.debtAssetPrice,
    402
            debtToCover: params.debtToCover,
    403
            collateralFactor: params.collateralFactor,
    404
            liquidationBonus: liquidationBonus,
    405
            healthFactor: params.healthFactor,
    406
            targetHealthFactor: params.targetHealthFactor
    407
          })
    408
        );
    409
    410 10×
        uint256 collateralToLiquidate = debtToLiquidate.mulDivDown(
    411 21×
          params.debtAssetPrice * params.collateralAssetUnit * liquidationBonus,
    412 20×
          params.debtAssetUnit * params.collateralAssetPrice * PercentageMath.PERCENTAGE_FACTOR
    413
        );
    414
    415 15×
        bool leavesCollateralDust = collateralToLiquidate < params.collateralReserveBalance &&
    416 16×
          (params.collateralReserveBalance - collateralToLiquidate).mulDivDown(
    417
            params.collateralAssetPrice.toWad(),
    418
            params.collateralAssetUnit
    419
          ) <
    420
          DUST_LIQUIDATION_THRESHOLD;
    421
    422
        if (
    423
          collateralToLiquidate > params.collateralReserveBalance ||
    424
          (leavesCollateralDust && debtToLiquidate < params.debtReserveBalance)
    425
        ) {
    426
          collateralToLiquidate = params.collateralReserveBalance;
    427
    428
          // - `debtToLiquidate` is decreased if `collateralToLiquidate > params.collateralReserveBalance` (if so, debt dust could remain).
    429
          // - `debtToLiquidate` is increased if `(leavesCollateralDust && debtToLiquidate < params.debtReserveBalance)`, ensuring collateral reserve
    430
          //   is fully liquidated (potentially bypassing the target health factor). Can only increase by at most `DUST_LIQUIDATION_THRESHOLD` (in
    431
          //   value terms). Since debt dust condition was enforced, it is guaranteed that `debtToLiquidate` will never exceed `params.debtReserveBalance`.
    432
          debtToLiquidate = collateralToLiquidate.mulDivUp(
    433 20×
            params.collateralAssetPrice * params.debtAssetUnit * PercentageMath.PERCENTAGE_FACTOR,
    434 21×
            params.debtAssetPrice * params.collateralAssetUnit * liquidationBonus
    435
          );
    436
        }
    437
    438
        // revert if the liquidator does not cover the necessary debt to prevent dust from remaining
    439 10×
        require(params.debtToCover >= debtToLiquidate, ISpoke.MustNotLeaveDust());
    440
    441
        uint256 collateralToLiquidator = collateralToLiquidate -
    442
          collateralToLiquidate.mulDivDown(
    443 15×
            params.liquidationFee * (liquidationBonus - PercentageMath.PERCENTAGE_FACTOR),
    444
            liquidationBonus * PercentageMath.PERCENTAGE_FACTOR
    445
          );
    446
    447
        return
    448 25×
          LiquidationAmounts({
    449
            collateralToLiquidate: collateralToLiquidate,
    450
            collateralToLiquidator: collateralToLiquidator,
    451
            debtToLiquidate: debtToLiquidate
    452
          });
    453
      }
    454
    455
      /// @notice Calculates the debt that should be liquidated.
    456
      /// @dev Generally, it returns the minimum of `debtToCover`, `debtReserveBalance` and `debtToTarget`.
    457
      /// If debt dust would be left behind, it returns `debtReserveBalance` to ensure the debt is fully cleared and no dust is left.
    458
      function _calculateDebtToLiquidate(
    459
        CalculateDebtToLiquidateParams memory params
    460
      ) internal pure returns (uint256) {
    461
        uint256 debtToLiquidate = params.debtReserveBalance;
    462
        if (params.debtToCover < debtToLiquidate) {
    463
          debtToLiquidate = params.debtToCover;
    464
        }
    465
    466
        uint256 debtToTarget = _calculateDebtToTargetHealthFactor(
    467 35×
          CalculateDebtToTargetHealthFactorParams({
    468
            totalDebtValue: params.totalDebtValue,
    469
            debtAssetUnit: params.debtAssetUnit,
    470
            debtAssetPrice: params.debtAssetPrice,
    471
            collateralFactor: params.collateralFactor,
    472
            liquidationBonus: params.liquidationBonus,
    473
            healthFactor: params.healthFactor,
    474
            targetHealthFactor: params.targetHealthFactor
    475
          })
    476
        );
    477
        if (debtToTarget < debtToLiquidate) {
    478
          debtToLiquidate = debtToTarget;
    479
        }
    480
    481 14×
        bool leavesDebtDust = debtToLiquidate < params.debtReserveBalance &&
    482 11×
          (params.debtReserveBalance - debtToLiquidate).mulDivDown(
    483
            params.debtAssetPrice.toWad(),
    484
            params.debtAssetUnit
    485
          ) <
    486
          DUST_LIQUIDATION_THRESHOLD;
    487
    488
        if (leavesDebtDust) {
    489
          // target health factor is bypassed to prevent leaving dust
    490 0
          debtToLiquidate = params.debtReserveBalance;
    491
        }
    492
    493
        return debtToLiquidate;
    494
      }
    495
    496
      /// @notice Calculates the amount of debt needed to be liquidated to restore a position to the target health factor.
    497
      function _calculateDebtToTargetHealthFactor(
    498
        CalculateDebtToTargetHealthFactorParams memory params
    499
      ) internal pure returns (uint256) {
    500 12×
        uint256 liquidationPenalty = params.liquidationBonus.bpsToWad().percentMulUp(
    501
          params.collateralFactor
    502
        );
    503
    504
        // denominator cannot be zero as `liquidationPenalty` is always < PercentageMath.PERCENTAGE_FACTOR
    505
        // `liquidationBonus.percentMulUp(collateralFactor) < PercentageMath.PERCENTAGE_FACTOR` is enforced in `_validateDynamicReserveConfig`
    506
        // and targetHealthFactor is always >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD
    507
        return
    508
          params.totalDebtValue.mulDivUp(
    509 24×
            params.debtAssetUnit * (params.targetHealthFactor - params.healthFactor),
    510 25×
            (params.targetHealthFactor - liquidationPenalty) * params.debtAssetPrice.toWad()
    511
          );
    512
      }
    513
    514
      /// @notice Returns if the liquidation results in deficit.
    515
      function _evaluateDeficit(
    516
        bool isCollateralPositionEmpty,
    517
        bool isDebtPositionEmpty,
    518
        uint256 activeCollateralCount,
    519
        uint256 borrowedCount
    520
      ) internal pure returns (bool) {
    521 14×
        if (!isCollateralPositionEmpty || activeCollateralCount > 1) {
    522
          return false;
    523
        }
    524
        return !isDebtPositionEmpty || borrowedCount > 1;
    525
      }
    526
    }
    63% src/spoke/libraries/PositionStatusMap.sol
    Lines covered: 46 / 72 (63%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {LibBit} from 'src/dependencies/solady/LibBit.sol';
    6
    import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    7
    8
    /// @title PositionStatusMap Library
    9
    /// @author Aave Labs
    10
    /// @notice Implements the bitmap logic to handle the user configuration.
    11 0
    library PositionStatusMap {
    12
      using PositionStatusMap for *;
    13
      using LibBit for uint256;
    14
    15 0
      uint256 internal constant NOT_FOUND = type(uint256).max;
    16
    17
      uint256 internal constant BORROWING_MASK =
    18
        0x5555555555555555555555555555555555555555555555555555555555555555;
    19
      uint256 internal constant COLLATERAL_MASK =
    20
        0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;
    21
    22
      /// @notice Sets if the user is borrowing the specified reserve.
    23
      function setBorrowing(
    24
        ISpoke.PositionStatus storage self,
    25
        uint256 reserveId,
    26
        bool borrowing
    27
      ) internal {
    28 0
        unchecked {
    29
          uint256 bit = 1 << ((reserveId % 128) << 1);
    30
          if (borrowing) {
    31
            self.map[reserveId.bucketId()] |= bit;
    32
          } else {
    33 0
            self.map[reserveId.bucketId()] &= ~bit;
    34
          }
    35
        }
    36
      }
    37
    38
      /// @notice Sets if the user is using as collateral the specified reserve.
    39
      function setUsingAsCollateral(
    40
        ISpoke.PositionStatus storage self,
    41
        uint256 reserveId,
    42
        bool usingAsCollateral
    43
      ) internal {
    44
        unchecked {
    45
          uint256 bit = 1 << (((reserveId % 128) << 1) + 1);
    46
          if (usingAsCollateral) {
    47 25×
            self.map[reserveId.bucketId()] |= bit;
    48
          } else {
    49 26×
            self.map[reserveId.bucketId()] &= ~bit;
    50
          }
    51
        }
    52
      }
    53
    54
      /// @notice Returns if a user is using the specified reserve for borrowing or as collateral.
    55 0
      function isUsingAsCollateralOrBorrowing(
    56
        ISpoke.PositionStatus storage self,
    57
        uint256 reserveId
    58 0
      ) internal view returns (bool) {
    59
        unchecked {
    60 0
          return (self.getBucketWord(reserveId) >> ((reserveId % 128) << 1)) & 3 != 0;
    61
        }
    62
      }
    63
    64
      /// @notice Returns if a user is using the specified reserve for borrowing.
    65
      function isBorrowing(
    66
        ISpoke.PositionStatus storage self,
    67
        uint256 reserveId
    68
      ) internal view returns (bool) {
    69
        unchecked {
    70 11×
          return (self.getBucketWord(reserveId) >> ((reserveId % 128) << 1)) & 1 != 0;
    71
        }
    72
      }
    73
    74
      /// @notice Returns if a user is using the specified reserve as collateral.
    75 10×
      function isUsingAsCollateral(
    76
        ISpoke.PositionStatus storage self,
    77
        uint256 reserveId
    78
      ) internal view returns (bool) {
    79
        unchecked {
    80 40×
          return (self.getBucketWord(reserveId) >> (((reserveId % 128) << 1) + 1)) & 1 != 0;
    81
        }
    82
      }
    83
    84
      /// @notice Counts the number of reserves enabled as collateral.
    85
      /// @dev Disregards potential dirty bits set after `reserveCount`.
    86
      /// @param reserveCount The current `reserveCount`, to avoid reading uninitialized buckets.
    87
      function collateralCount(
    88
        ISpoke.PositionStatus storage self,
    89
        uint256 reserveCount
    90
      ) internal view returns (uint256) {
    91
        unchecked {
    92
          uint256 bucket = reserveCount.bucketId();
    93 21×
          uint256 count = self.map[bucket].isolateCollateralUntil(reserveCount).popCount();
    94
          while (bucket != 0) {
    95 0
            count += self.map[--bucket].isolateCollateral().popCount();
    96
          }
    97 0
          return count;
    98
        }
    99
      }
    100
    101
      /// @notice Finds the previous borrowing or collateralized reserve strictly before `fromReserveId`.
    102
      /// @dev The search starts at `fromReserveId` (exclusive) and scans backward across buckets.
    103
      /// @dev Returns `NOT_FOUND` if no borrowing or collateralized reserve exists before the bound.
    104
      /// @dev Ignores dirty bits beyond the configured `reserveCount` within the last bucket.
    105
      /// @param fromReserveId The identifier of the reserve to start searching from.
    106
      /// @return reserveId The reserve identifier for the next reserve that is borrowed or used as collateral.
    107
      /// @return borrowing True if the next reserveId is borrowed.
    108
      /// @return collateral True if the next reserveId is used as collateral.
    109
      function next(
    110
        ISpoke.PositionStatus storage self,
    111
        uint256 fromReserveId
    112
      ) internal view returns (uint256, bool, bool) {
    113
        unchecked {
    114
          uint256 bucket = fromReserveId.bucketId();
    115 14×
          uint256 map = self.map[bucket];
    116
          uint256 setBitId = map.isolateUntil(fromReserveId).fls();
    117 16×
          while (setBitId == 256 && bucket != 0) {
    118 0
            map = self.map[--bucket];
    119 0
            setBitId = map.fls();
    120
          }
    121
          if (setBitId == 256) {
    122 13×
            return (NOT_FOUND, false, false);
    123
          } else {
    124
            uint256 word = map >> ((setBitId >> 1) << 1);
    125 15×
            return (setBitId.fromBitId(bucket), word & 1 != 0, word & 2 != 0);
    126
          }
    127
        }
    128
      }
    129
    130
      /// @notice Finds the previous borrowed reserve strictly before `fromReserveId`.
    131
      /// @dev The search starts at `fromReserveId` (exclusive) and scans backward across buckets.
    132
      /// @dev Returns `NOT_FOUND` if no borrowed reserve exists before the bound.
    133
      /// @dev Ignores dirty bits beyond the configured `reserveCount` within the last bucket.
    134
      /// @param fromReserveId The exclusive upper bound to start from (this reserveId is not considered).
    135
      /// @return The previous borrowed reserveId, or `NOT_FOUND` if none is found.
    136
      function nextBorrowing(
    137
        ISpoke.PositionStatus storage self,
    138
        uint256 fromReserveId
    139
      ) internal view returns (uint256) {
    140
        unchecked {
    141
          uint256 bucket = fromReserveId.bucketId();
    142 24×
          uint256 setBitId = self.map[bucket].isolateBorrowingUntil(fromReserveId).fls();
    143 16×
          while (setBitId == 256 && bucket != 0) {
    144 0
            setBitId = self.map[--bucket].isolateBorrowing().fls();
    145
          }
    146 10×
          return setBitId == 256 ? NOT_FOUND : setBitId.fromBitId(bucket);
    147
        }
    148
      }
    149
    150
      /// @notice Finds the previous collateral reserve strictly before `fromReserveId`.
    151
      /// @dev The search starts at `fromReserveId` (exclusive) and scans backward across buckets.
    152
      /// @dev Returns `NOT_FOUND` if no collateral reserve exists before the bound.
    153
      /// @dev Ignores dirty bits beyond the configured `reserveCount` within the last bucket.
    154
      /// @param fromReserveId The exclusive upper bound to start from (this reserveId is not considered).
    155
      /// @return The previous collateral reserveId, or `NOT_FOUND` if none is found.
    156 0
      function nextCollateral(
    157
        ISpoke.PositionStatus storage self,
    158
        uint256 fromReserveId
    159 0
      ) internal view returns (uint256) {
    160
        unchecked {
    161 0
          uint256 bucket = fromReserveId.bucketId();
    162 0
          uint256 setBitId = self.map[bucket].isolateCollateralUntil(fromReserveId).fls();
    163 0
          while (setBitId == 256 && bucket != 0) {
    164 0
            setBitId = self.map[--bucket].isolateCollateral().fls();
    165
          }
    166 0
          return setBitId == 256 ? NOT_FOUND : setBitId.fromBitId(bucket);
    167
        }
    168
      }
    169
    170
      /// @notice Returns the word containing the reserve state in the bitmap.
    171 12×
      function getBucketWord(
    172
        ISpoke.PositionStatus storage self,
    173
        uint256 reserveId
    174
      ) internal view returns (uint256) {
    175 36×
        return self.map[reserveId.bucketId()];
    176
      }
    177
    178
      /// @notice Converts a reserveId to its corresponding bucketId.
    179
      function bucketId(uint256 reserveId) internal pure returns (uint256 wordId) {
    180
        assembly ('memory-safe') {
    181 24×
          wordId := shr(7, reserveId)
    182
        }
    183
      }
    184
    185
      /// @notice Converts a bit index to its corresponding reserve index in the bitmap.
    186
      /// @dev BitId 0, 1 correspond to reserveId 0; BitId 2, 3 correspond to reserveId 1; etc.
    187
      function fromBitId(uint256 bitId, uint256 bucket) internal pure returns (uint256 reserveId) {
    188
        assembly ('memory-safe') {
    189 18×
          reserveId := add(shr(1, bitId), shl(7, bucket))
    190
        }
    191
      }
    192
    193
      /// @notice Isolates the borrowing bits from word.
    194 0
      function isolateBorrowing(uint256 word) internal pure returns (uint256 ret) {
    195
        assembly ('memory-safe') {
    196 0
          ret := and(word, BORROWING_MASK)
    197
        }
    198
      }
    199
    200
      /// @notice Returns masked `word` containing only borrowing bits from the first reserve up to `reserveCount`.
    201 0
      function isolateBorrowingUntil(
    202
        uint256 word,
    203
        uint256 reserveCount
    204
      ) internal pure returns (uint256 ret) {
    205
        // ret = word & (BORROWING_MASK >> (256 - ((reserveCount % 128) << 1)));
    206
        assembly ('memory-safe') {
    207 10×
          ret := and(word, shr(sub(256, shl(1, mod(reserveCount, 128))), BORROWING_MASK))
    208
        }
    209
      }
    210
    211
      /// @notice Returns masked `word` containing bits from the first reserve up to `reserveCount`.
    212 0
      function isolateUntil(uint256 word, uint256 reserveCount) internal pure returns (uint256 ret) {
    213
        // ret = word & (type(uint256).max >> (256 - ((reserveCount % 128) << 1)));
    214
        assembly ('memory-safe') {
    215 11×
          ret := and(word, shr(sub(256, shl(1, mod(reserveCount, 128))), not(0)))
    216
        }
    217
      }
    218
    219
      /// @notice Isolates the collateral bits from word.
    220 0
      function isolateCollateral(uint256 word) internal pure returns (uint256 ret) {
    221
        assembly ('memory-safe') {
    222 0
          ret := and(word, COLLATERAL_MASK)
    223
        }
    224
      }
    225
    226
      /// @notice Returns masked `word` containing only collateral bits from the first reserve up to `reserveCount`.
    227 0
      function isolateCollateralUntil(
    228
        uint256 word,
    229
        uint256 reserveCount
    230
      ) internal pure returns (uint256 ret) {
    231
        // ret = word & (COLLATERAL_MASK >> (256 - ((reserveCount % 128) << 1)));
    232
        assembly ('memory-safe') {
    233 11×
          ret := and(word, shr(sub(256, shl(1, mod(reserveCount, 128))), COLLATERAL_MASK))
    234
        }
    235
      }
    236
    }
    52% src/spoke/libraries/ReserveFlagsMap.sol
    Lines covered: 19 / 36 (52%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {ReserveFlags} from 'src/spoke/interfaces/ISpoke.sol';
    6
    7
    /// @title ReserveFlags Library
    8
    /// @author Aave Labs
    9
    /// @notice Implements the bitmap logic to handle the Reserve flags configuration.
    10 0
    library ReserveFlagsMap {
    11
      /// @dev Mask for the `paused` flag.
    12
      uint8 internal constant PAUSED_MASK = 0x01;
    13
      /// @dev Mask for the `frozen` flag.
    14
      uint8 internal constant FROZEN_MASK = 0x02;
    15
      /// @dev Mask for the `borrowable` flag.
    16
      uint8 internal constant BORROWABLE_MASK = 0x04;
    17
      /// @dev Mask for the `liquidatable` flag.
    18
      uint8 internal constant LIQUIDATABLE_MASK = 0x08;
    19
      /// @dev Mask for the `receiveSharesEnabled` flag.
    20
      uint8 internal constant RECEIVE_SHARES_ENABLED_MASK = 0x10;
    21
    22
      /// @notice Initializes the ReserveFlags with the given values.
    23
      /// @param initPaused The initial `paused` flag status.
    24
      /// @param initFrozen The initial `frozen` flag status.
    25
      /// @param initBorrowable The initial `borrowable` flag status.
    26
      /// @param initLiquidatable The initial `liquidatable` flag status.
    27
      /// @param initReceiveSharesEnabled The initial `receiveSharesEnabled` flag status.
    28
      /// @return The initialized ReserveFlags.
    29
      function create(
    30
        bool initPaused,
    31
        bool initFrozen,
    32
        bool initBorrowable,
    33
        bool initLiquidatable,
    34
        bool initReceiveSharesEnabled
    35
      ) internal pure returns (ReserveFlags) {
    36
        uint8 flags = 0;
    37
        flags = _setStatus(flags, PAUSED_MASK, initPaused);
    38
        flags = _setStatus(flags, FROZEN_MASK, initFrozen);
    39
        flags = _setStatus(flags, BORROWABLE_MASK, initBorrowable);
    40
        flags = _setStatus(flags, LIQUIDATABLE_MASK, initLiquidatable);
    41
        flags = _setStatus(flags, RECEIVE_SHARES_ENABLED_MASK, initReceiveSharesEnabled);
    42
        return ReserveFlags.wrap(flags);
    43
      }
    44
    45
      /// @notice Sets the new status for the `paused` flag.
    46
      /// @param flags The current ReserveFlags.
    47
      /// @param status The new status for the `paused` flag.
    48
      /// @return The updated ReserveFlags.
    49 0
      function setPaused(ReserveFlags flags, bool status) internal pure returns (ReserveFlags) {
    50 0
        return ReserveFlags.wrap(_setStatus(ReserveFlags.unwrap(flags), PAUSED_MASK, status));
    51
      }
    52
    53
      /// @notice Sets the new status for the `frozen` flag.
    54
      /// @param flags The current ReserveFlags.
    55
      /// @param status The new status for the `frozen` flag.
    56
      /// @return The updated ReserveFlags.
    57 0
      function setFrozen(ReserveFlags flags, bool status) internal pure returns (ReserveFlags) {
    58 0
        return ReserveFlags.wrap(_setStatus(ReserveFlags.unwrap(flags), FROZEN_MASK, status));
    59
      }
    60
    61
      /// @notice Sets the new status for the `borrowable` flag.
    62
      /// @param flags The current ReserveFlags.
    63
      /// @param status The new status for the `borrowable` flag.
    64
      /// @return The updated ReserveFlags.
    65 0
      function setBorrowable(ReserveFlags flags, bool status) internal pure returns (ReserveFlags) {
    66 0
        return ReserveFlags.wrap(_setStatus(ReserveFlags.unwrap(flags), BORROWABLE_MASK, status));
    67
      }
    68
    69
      /// @notice Sets the new status for the `liquidatable` flag.
    70
      /// @param flags The current ReserveFlags.
    71
      /// @param status The new status for the `liquidatable` flag.
    72
      /// @return The updated ReserveFlags.
    73 0
      function setLiquidatable(ReserveFlags flags, bool status) internal pure returns (ReserveFlags) {
    74 0
        return ReserveFlags.wrap(_setStatus(ReserveFlags.unwrap(flags), LIQUIDATABLE_MASK, status));
    75
      }
    76
    77
      /// @notice Sets the new status for the `receiveSharesEnabled` flag.
    78
      /// @param flags The current ReserveFlags.
    79
      /// @param status The new status for the `receiveSharesEnabled` flag.
    80
      /// @return The updated ReserveFlags.
    81 0
      function setReceiveSharesEnabled(
    82
        ReserveFlags flags,
    83
        bool status
    84 0
      ) internal pure returns (ReserveFlags) {
    85
        return
    86
          ReserveFlags.wrap(
    87 0
            _setStatus(ReserveFlags.unwrap(flags), RECEIVE_SHARES_ENABLED_MASK, status)
    88
          );
    89
      }
    90
    91
      /// @notice Returns the `paused` flag status.
    92
      /// @param flags The current ReserveFlags.
    93
      /// @return True if the flag is set.
    94 0
      function paused(ReserveFlags flags) internal pure returns (bool) {
    95 18×
        return (ReserveFlags.unwrap(flags) & PAUSED_MASK) != 0;
    96
      }
    97
    98
      /// @notice Returns the `frozen` flag status.
    99
      /// @param flags The current ReserveFlags.
    100
      /// @return True if the flag is set.
    101 0
      function frozen(ReserveFlags flags) internal pure returns (bool) {
    102 13×
        return (ReserveFlags.unwrap(flags) & FROZEN_MASK) != 0;
    103
      }
    104
    105
      /// @notice Returns the `borrowable` flag status.
    106
      /// @param flags The current ReserveFlags.
    107
      /// @return True if the flag is set.
    108 0
      function borrowable(ReserveFlags flags) internal pure returns (bool) {
    109
        return (ReserveFlags.unwrap(flags) & BORROWABLE_MASK) != 0;
    110
      }
    111
    112
      /// @notice Returns the `liquidatable` flag status.
    113
      /// @param flags The current ReserveFlags.
    114
      /// @return True if the flag is set.
    115 0
      function liquidatable(ReserveFlags flags) internal pure returns (bool) {
    116
        return (ReserveFlags.unwrap(flags) & LIQUIDATABLE_MASK) != 0;
    117
      }
    118
    119
      /// @notice Returns the `receiveSharesEnabled` flag status.
    120
      /// @param flags The current ReserveFlags.
    121
      /// @return True if the flag is set.
    122 0
      function receiveSharesEnabled(ReserveFlags flags) internal pure returns (bool) {
    123
        return (ReserveFlags.unwrap(flags) & RECEIVE_SHARES_ENABLED_MASK) != 0;
    124
      }
    125
    126
      /// @notice Sets the new status for the given flag.
    127
      function _setStatus(uint8 flags, uint8 mask, bool status) private pure returns (uint8) {
    128 12×
        return status ? flags | mask : flags & ~mask;
    129
      }
    130
    }
    95% src/spoke/libraries/UserPositionDebt.sol
    Lines covered: 41 / 43 (95%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    6
    import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
    7
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    8
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    9
    import {Premium} from 'src/hub/libraries/Premium.sol';
    10
    import {IHubBase} from 'src/hub/interfaces/IHubBase.sol';
    11
    import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
    12
    13
    /// @title User Debt library
    14
    /// @author Aave Labs
    15
    /// @notice Implements debt calculations for user positions.
    16 0
    library UserPositionDebt {
    17
      using UserPositionDebt for ISpoke.UserPosition;
    18
      using SafeCast for *;
    19
      using PercentageMath for uint256;
    20
      using WadRayMath for *;
    21
      using MathUtils for *;
    22
    23
      /// @notice Applies the premium delta to the user position.
    24
      /// @param userPosition The user position.
    25
      /// @param premiumDelta The premium delta to apply.
    26
      function applyPremiumDelta(
    27
        ISpoke.UserPosition storage userPosition,
    28
        IHubBase.PremiumDelta memory premiumDelta
    29
      ) internal {
    30 58×
        userPosition.premiumShares = userPosition
    31
          .premiumShares
    32
          .add(premiumDelta.sharesDelta)
    33
          .toUint120();
    34 74×
        userPosition.premiumOffsetRay = (userPosition.premiumOffsetRay + premiumDelta.offsetRayDelta)
    35
          .toInt200();
    36
      }
    37
    38
      /// @notice Calculates the premium delta for a user position given a new risk premium.
    39
      /// @param userPosition The user position.
    40
      /// @param drawnSharesTaken The amount of drawn shares taken from the user position.
    41
      /// @param drawnIndex The current drawn index.
    42
      /// @param riskPremium The new risk premium, expressed in BPS.
    43
      /// @param restoredPremiumRay The amount of premium to be restored, expressed in asset units and scaled by RAY.
    44
      /// @return The calculated premium delta.
    45 18×
      function getPremiumDelta(
    46
        ISpoke.UserPosition storage userPosition,
    47
        uint256 drawnSharesTaken,
    48
        uint256 drawnIndex,
    49
        uint256 riskPremium,
    50
        uint256 restoredPremiumRay
    51
      ) internal view returns (IHubBase.PremiumDelta memory) {
    52 18×
        uint256 oldPremiumShares = userPosition.premiumShares;
    53 14×
        int256 oldPremiumOffsetRay = userPosition.premiumOffsetRay;
    54 12×
        uint256 premiumDebtRay = Premium.calculatePremiumRay({
    55
          premiumShares: oldPremiumShares,
    56
          premiumOffsetRay: oldPremiumOffsetRay,
    57
          drawnIndex: drawnIndex
    58
        });
    59 38×
        uint256 newPremiumShares = (userPosition.drawnShares - drawnSharesTaken).percentMulUp(
    60
          riskPremium
    61
        );
    62 28×
        int256 newPremiumOffsetRay = (newPremiumShares * drawnIndex).signedSub(
    63 12×
          premiumDebtRay - restoredPremiumRay
    64
        );
    65
    66
        return
    67 38×
          IHubBase.PremiumDelta({
    68 22×
            sharesDelta: newPremiumShares.signedSub(oldPremiumShares),
    69 12×
            offsetRayDelta: newPremiumOffsetRay - oldPremiumOffsetRay,
    70
            restoredPremiumRay: restoredPremiumRay
    71
          });
    72
      }
    73
    74
      /// @dev Calculates the drawn debt and premium debt to restore for the given user position and amount.
    75
      /// @param userPosition The user position.
    76
      /// @param drawnIndex The drawn index of the reserve.
    77
      /// @param amount The amount to restore.
    78
      /// @return The amount of drawn debt to restore, expressed in asset units.
    79
      /// @return The amount of premium debt to restore, expressed in asset units and scaled by RAY.
    80
      function calculateRestoreAmount(
    81
        ISpoke.UserPosition storage userPosition,
    82
        uint256 drawnIndex,
    83
        uint256 amount
    84
      ) internal view returns (uint256, uint256) {
    85 10×
        (uint256 drawnDebt, uint256 premiumDebtRay) = userPosition.getDebt(drawnIndex);
    86
        uint256 premiumDebt = premiumDebtRay.fromRayUp();
    87 10×
        if (amount >= drawnDebt + premiumDebt) {
    88
          return (drawnDebt, premiumDebtRay);
    89
        }
    90
    91
        if (amount < premiumDebt) {
    92
          // amount.toRay() cannot overflow here
    93
          uint256 amountRay = amount.toRay();
    94
          return (0, amountRay);
    95
        }
    96
        return (amount - premiumDebt, premiumDebtRay);
    97
      }
    98
    99
      /// @return The user's drawn debt, expressed in asset units.
    100
      /// @return The user's premium debt, expressed in asset units and scaled by RAY.
    101
      function getDebt(
    102
        ISpoke.UserPosition storage userPosition,
    103
        IHubBase hub,
    104
        uint256 assetId
    105
      ) internal view returns (uint256, uint256) {
    106 72×
        return userPosition.getDebt(hub.getAssetDrawnIndex(assetId));
    107
      }
    108
    109
      /// @return The user's drawn debt, expressed in asset units.
    110
      /// @return The user's premium debt, expressed in asset units and scaled by RAY.
    111
      function getDebt(
    112
        ISpoke.UserPosition storage userPosition,
    113
        uint256 drawnIndex
    114
      ) internal view returns (uint256, uint256) {
    115
        uint256 premiumDebtRay = _calculatePremiumRay(userPosition, drawnIndex);
    116 12×
        return (userPosition.drawnShares.rayMulUp(drawnIndex), premiumDebtRay);
    117
      }
    118
    119
      /// @dev Calculates the premium debt of a user position with full precision.
    120
      /// @param userPosition The user position.
    121
      /// @param drawnIndex The current drawn index.
    122
      /// @return The premium debt, expressed in asset units and scaled by RAY.
    123
      function _calculatePremiumRay(
    124
        ISpoke.UserPosition storage userPosition,
    125
        uint256 drawnIndex
    126
      ) internal view returns (uint256) {
    127 0
        return
    128
          Premium.calculatePremiumRay({
    129
            premiumShares: userPosition.premiumShares,
    130
            premiumOffsetRay: userPosition.premiumOffsetRay,
    131
            drawnIndex: drawnIndex
    132
          });
    133
      }
    134
    }
    0% src/utils/Multicall.sol
    Lines covered: 0 / 8 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {IMulticall} from 'src/interfaces/IMulticall.sol';
    6
    7
    /// @title Multicall
    8
    /// @author Aave Labs
    9
    /// @notice This contract allows for batching multiple calls into a single call.
    10
    /// @dev Inspired by the OpenZeppelin Multicall contract.
    11
    abstract contract Multicall is IMulticall {
    12
      /// @inheritdoc IMulticall
    13 0
      function multicall(bytes[] calldata data) external returns (bytes[] memory) {
    14 0
        bytes[] memory results = new bytes[](data.length);
    15 0
        for (uint256 i; i < data.length; ++i) {
    16 0
          (bool ok, bytes memory res) = address(this).delegatecall(data[i]);
    17
    18
          assembly ('memory-safe') {
    19 0
            if iszero(ok) {
    20 0
              revert(add(res, 32), mload(res)) // bubble up first revert
    21
            }
    22
          }
    23
    24 0
          results[i] = res;
    25
        }
    26 0
        return results;
    27
      }
    28
    }
    0% src/utils/NoncesKeyed.sol
    Lines covered: 0 / 14 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {INoncesKeyed} from 'src/interfaces/INoncesKeyed.sol';
    6
    7
    /// @notice Provides tracking nonces for addresses. Supports key-ed nonces, where nonces will only increment for each key.
    8
    /// @author Modified from OpenZeppelin https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/NoncesKeyed.sol
    9
    /// @dev Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system].
    10 0
    contract NoncesKeyed is INoncesKeyed {
    11
      mapping(address owner => mapping(uint192 key => uint64 nonce)) private _nonces;
    12
    13
      /// @inheritdoc INoncesKeyed
    14 0
      function useNonce(uint192 key) external returns (uint256) {
    15 0
        return _useNonce(msg.sender, key);
    16
      }
    17
    18
      /// @inheritdoc INoncesKeyed
    19 0
      function nonces(address owner, uint192 key) external view returns (uint256) {
    20 0
        return _pack(key, _nonces[owner][key]);
    21
      }
    22
    23
      /// @notice Consumes the next unused nonce for an address and key.
    24
      /// @dev Returns the current packed `keyNonce`. Consumed nonce is increased, so calling this function twice
    25
      /// with the same arguments will return different (sequential) results.
    26 0
      function _useNonce(address owner, uint192 key) internal returns (uint256) {
    27
        // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
    28
        // decremented or reset. This guarantees that the nonce never overflows.
    29
        unchecked {
    30
          // It is important to do x++ and not ++x here.
    31 0
          return _pack(key, _nonces[owner][key]++);
    32
        }
    33
      }
    34
    35
      /// @dev Same as `_useNonce` but checking that `nonce` is the next valid for `owner` for specified packed `keyNonce`.
    36 0
      function _useCheckedNonce(address owner, uint256 keyNonce) internal {
    37 0
        (uint192 key, ) = _unpack(keyNonce);
    38 0
        uint256 current = _useNonce(owner, key);
    39 0
        require(keyNonce == current, InvalidAccountNonce(owner, current));
    40
      }
    41
    42
      /// @dev Pack key and nonce into a keyNonce.
    43 0
      function _pack(uint192 key, uint64 nonce) private pure returns (uint256) {
    44 0
        return (uint256(key) << 64) | nonce;
    45
      }
    46
    47
      /// @dev Unpack a keyNonce into its key and nonce components.
    48
      function _unpack(uint256 keyNonce) private pure returns (uint192 key, uint64 nonce) {
    49 0
        return (uint192(keyNonce >> 64), uint64(keyNonce));
    50
      }
    51
    }
    0% src/utils/Rescuable.sol
    Lines covered: 0 / 9 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.20;
    4
    5
    import {SafeERC20, IERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
    6
    import {Address} from 'src/dependencies/openzeppelin/Address.sol';
    7
    import {IRescuable} from 'src/interfaces/IRescuable.sol';
    8
    9
    /// @title Rescuable
    10
    /// @author Aave Labs
    11
    /// @notice Contract that allows for the rescue of tokens and native assets.
    12
    abstract contract Rescuable is IRescuable {
    13
      using SafeERC20 for IERC20;
    14
    15
      modifier onlyRescueGuardian() {
    16 0
        _checkRescueGuardian();
    17
        _;
    18
      }
    19
    20
      /// @inheritdoc IRescuable
    21 0
      function rescueToken(address token, address to, uint256 amount) external onlyRescueGuardian {
    22 0
        IERC20(token).safeTransfer(to, amount);
    23
      }
    24
    25
      /// @inheritdoc IRescuable
    26 0
      function rescueNative(address to, uint256 amount) external onlyRescueGuardian {
    27 0
        Address.sendValue(payable(to), amount);
    28
      }
    29
    30
      /// @inheritdoc IRescuable
    31 0
      function rescueGuardian() external view returns (address) {
    32 0
        return _rescueGuardian();
    33
      }
    34
    35
      function _rescueGuardian() internal view virtual returns (address);
    36
    37 0
      function _checkRescueGuardian() internal view virtual {
    38 0
        require(_rescueGuardian() == msg.sender, OnlyRescueGuardian());
    39
      }
    40
    }
    31% tests/Base.t.sol
    Lines covered: 436 / 1394 (31%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.0;
    4
    5
    import {Test} from 'forge-std/Test.sol';
    6
    import {stdError} from 'forge-std/StdError.sol';
    7
    import {stdMath} from 'forge-std/StdMath.sol';
    8
    import {StdStorage, stdStorage} from 'forge-std/StdStorage.sol';
    9
    import {Vm, VmSafe} from 'forge-std/Vm.sol';
    10
    import {console2 as console} from 'forge-std/console2.sol';
    11
    12
    // dependencies
    13
    import {AggregatorV3Interface} from 'src/dependencies/chainlink/AggregatorV3Interface.sol';
    14
    import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol';
    15
    import {IERC20Metadata} from 'src/dependencies/openzeppelin/IERC20Metadata.sol';
    16
    import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
    17
    import {IERC20Errors} from 'src/dependencies/openzeppelin/IERC20Errors.sol';
    18
    import {IERC20} from 'src/dependencies/openzeppelin/IERC20.sol';
    19
    import {IERC5267} from 'src/dependencies/openzeppelin/IERC5267.sol';
    20
    import {AccessManager} from 'src/dependencies/openzeppelin/AccessManager.sol';
    21
    import {IAccessManager} from 'src/dependencies/openzeppelin/IAccessManager.sol';
    22
    import {IAccessManaged} from 'src/dependencies/openzeppelin/IAccessManaged.sol';
    23
    import {AuthorityUtils} from 'src/dependencies/openzeppelin/AuthorityUtils.sol';
    24
    import {Ownable2Step, Ownable} from 'src/dependencies/openzeppelin/Ownable2Step.sol';
    25
    import {Math} from 'src/dependencies/openzeppelin/Math.sol';
    26
    import {WETH9} from 'src/dependencies/weth/WETH9.sol';
    27
    import {LibBit} from 'src/dependencies/solady/LibBit.sol';
    28
    29
    import {Initializable} from 'src/dependencies/openzeppelin-upgradeable/Initializable.sol';
    30
    import {IERC1967} from 'src/dependencies/openzeppelin/IERC1967.sol';
    31
    32
    // shared
    33
    import {WadRayMath} from 'src/libraries/math/WadRayMath.sol';
    34
    import {MathUtils} from 'src/libraries/math/MathUtils.sol';
    35
    import {PercentageMath} from 'src/libraries/math/PercentageMath.sol';
    36
    import {EIP712Types} from 'src/libraries/types/EIP712Types.sol';
    37
    import {Roles} from 'src/libraries/types/Roles.sol';
    38
    import {Rescuable, IRescuable} from 'src/utils/Rescuable.sol';
    39
    import {NoncesKeyed, INoncesKeyed} from 'src/utils/NoncesKeyed.sol';
    40
    import {UnitPriceFeed} from 'src/misc/UnitPriceFeed.sol';
    41
    import {AccessManagerEnumerable} from 'src/access/AccessManagerEnumerable.sol';
    42
    43
    // hub
    44
    import {HubConfigurator, IHubConfigurator} from 'src/hub/HubConfigurator.sol';
    45
    import {Hub, IHub, IHubBase} from 'src/hub/Hub.sol';
    46
    import {SharesMath} from 'src/hub/libraries/SharesMath.sol';
    47
    import {AssetInterestRateStrategy, IAssetInterestRateStrategy, IBasicInterestRateStrategy} from 'src/hub/AssetInterestRateStrategy.sol';
    48
    49
    // spoke
    50
    import {Spoke, ISpoke, ISpokeBase} from 'src/spoke/Spoke.sol';
    51
    import {TreasurySpoke, ITreasurySpoke} from 'src/spoke/TreasurySpoke.sol';
    52
    import {IPriceOracle} from 'src/spoke/interfaces/IPriceOracle.sol';
    53
    import {AaveOracle} from 'src/spoke/AaveOracle.sol';
    54
    import {IAaveOracle} from 'src/spoke/interfaces/IAaveOracle.sol';
    55
    import {SpokeConfigurator, ISpokeConfigurator} from 'src/spoke/SpokeConfigurator.sol';
    56
    import {SpokeInstance} from 'src/spoke/instances/SpokeInstance.sol';
    57
    import {PositionStatusMap} from 'src/spoke/libraries/PositionStatusMap.sol';
    58
    import {ReserveFlags, ReserveFlagsMap} from 'src/spoke/libraries/ReserveFlagsMap.sol';
    59
    import {LiquidationLogic} from 'src/spoke/libraries/LiquidationLogic.sol';
    60
    import {KeyValueList} from 'src/spoke/libraries/KeyValueList.sol';
    61
    62
    // position manager
    63
    import {GatewayBase, IGatewayBase} from 'src/position-manager/GatewayBase.sol';
    64
    import {NativeTokenGateway, INativeTokenGateway} from 'src/position-manager/NativeTokenGateway.sol';
    65
    import {SignatureGateway, ISignatureGateway} from 'src/position-manager/SignatureGateway.sol';
    66
    67
    // test
    68
    import {Constants} from 'tests/Constants.sol';
    69
    import {Utils} from 'tests/Utils.sol';
    70
    71
    // mocks
    72
    import {TestnetERC20} from 'tests/mocks/TestnetERC20.sol';
    73
    import {MockERC20} from 'tests/mocks/MockERC20.sol';
    74
    import {MockPriceFeed} from 'tests/mocks/MockPriceFeed.sol';
    75
    import {PositionStatusMapWrapper} from 'tests/mocks/PositionStatusMapWrapper.sol';
    76
    import {RescuableWrapper} from 'tests/mocks/RescuableWrapper.sol';
    77
    import {GatewayBaseWrapper} from 'tests/mocks/GatewayBaseWrapper.sol';
    78
    import {MockNoncesKeyed} from 'tests/mocks/MockNoncesKeyed.sol';
    79
    import {MockSpoke} from 'tests/mocks/MockSpoke.sol';
    80
    import {MockERC1271Wallet} from 'tests/mocks/MockERC1271Wallet.sol';
    81
    import {MockSpokeInstance} from 'tests/mocks/MockSpokeInstance.sol';
    82
    import {MockSkimSpoke} from 'tests/mocks/MockSkimSpoke.sol';
    83
    84
    abstract contract Base is Test {
    85
      using stdStorage for StdStorage;
    86
      using WadRayMath for *;
    87
      using SharesMath for uint256;
    88
      using PercentageMath for uint256;
    89
      using SafeCast for *;
    90
      using MathUtils for uint256;
    91
      using ReserveFlagsMap for ReserveFlags;
    92
    93
      bytes32 internal constant ERC1967_ADMIN_SLOT =
    94 0
        0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    95
      bytes32 internal constant IMPLEMENTATION_SLOT =
    96 0
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    97
    98
      uint256 internal constant MAX_SUPPLY_AMOUNT = 1e30;
    99 0
      uint256 internal constant MIN_TOKEN_DECIMALS_SUPPORTED = 6;
    100
      uint256 internal constant MAX_TOKEN_DECIMALS_SUPPORTED = 18;
    101
      uint256 internal constant MAX_SUPPLY_ASSET_UNITS =
    102 60×
        MAX_SUPPLY_AMOUNT / 10 ** MAX_TOKEN_DECIMALS_SUPPORTED;
    103
      uint256 internal MAX_SUPPLY_AMOUNT_USDX;
    104
      uint256 internal MAX_SUPPLY_AMOUNT_DAI;
    105
      uint256 internal MAX_SUPPLY_AMOUNT_WBTC;
    106
      uint256 internal MAX_SUPPLY_AMOUNT_WETH;
    107
      uint256 internal MAX_SUPPLY_AMOUNT_USDY;
    108
      uint256 internal MAX_SUPPLY_AMOUNT_USDZ;
    109
      uint256 internal constant MAX_SUPPLY_IN_BASE_CURRENCY = 1e39;
    110
      uint24 internal constant MIN_COLLATERAL_RISK_BPS = 1;
    111 0
      uint24 internal constant MAX_COLLATERAL_RISK_BPS = 1000_00;
    112 0
      uint256 internal constant MAX_BORROW_RATE = 1000_00; // matches AssetInterestRateStrategy
    113 0
      uint256 internal constant MIN_OPTIMAL_RATIO = 1_00; // 1.00% in BPS, matches AssetInterestRateStrategy
    114 0
      uint256 internal constant MAX_OPTIMAL_RATIO = 99_00; // 99.00% in BPS, matches AssetInterestRateStrategy
    115 0
      uint256 internal constant MAX_SKIP_TIME = 10_000 days;
    116
      uint32 internal constant MIN_LIQUIDATION_BONUS = uint32(PercentageMath.PERCENTAGE_FACTOR); // 100% == 0% bonus
    117 0
      uint32 internal constant MAX_LIQUIDATION_BONUS = 150_00; // 50% bonus
    118
      uint16 internal constant MAX_LIQUIDATION_BONUS_FACTOR = uint16(PercentageMath.PERCENTAGE_FACTOR); // 100%
    119 0
      uint16 internal constant MAX_LIQUIDATION_FEE = 100_00;
    120
      uint16 internal constant MIN_LIQUIDATION_FEE = 0;
    121 0
      uint128 internal constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
    122 0
      uint128 internal constant MIN_CLOSE_FACTOR = 1e18;
    123 0
      uint128 internal constant MAX_CLOSE_FACTOR = 2e18;
    124
      uint256 internal constant MAX_COLLATERAL_FACTOR = 100_00;
    125 0
      uint256 internal constant MAX_ASSET_PRICE = 1e8 * 1e8; // $100M per token
    126
      uint256 internal constant MAX_LIQUIDATION_PROTOCOL_FEE_PERCENTAGE =
    127
        PercentageMath.PERCENTAGE_FACTOR;
    128
      IHubBase.PremiumDelta internal ZERO_PREMIUM_DELTA = ZERO_PREMIUM_DELTA;
    129
    130
      IAaveOracle internal oracle1;
    131
      IAaveOracle internal oracle2;
    132
      IAaveOracle internal oracle3;
    133
      IHub internal hub1;
    134
      ITreasurySpoke internal treasurySpoke;
    135
      ISpoke internal spoke1;
    136
      ISpoke internal spoke2;
    137
      ISpoke internal spoke3;
    138
      AssetInterestRateStrategy internal irStrategy;
    139
      IAccessManager internal accessManager;
    140
    141 27×
      address internal alice = makeAddr('alice');
    142 35×
      address internal bob = makeAddr('bob');
    143 35×
      address internal carol = makeAddr('carol');
    144 35×
      address internal derl = makeAddr('derl');
    145
    146 35×
      address internal ADMIN = makeAddr('ADMIN');
    147 35×
      address internal HUB_ADMIN = makeAddr('HUB_ADMIN');
    148 35×
      address internal SPOKE_ADMIN = makeAddr('SPOKE_ADMIN');
    149 36×
      address internal USER_POSITION_UPDATER = makeAddr('USER_POSITION_UPDATER');
    150 34×
      address internal TREASURY_ADMIN = makeAddr('TREASURY_ADMIN');
    151 35×
      address internal LIQUIDATOR = makeAddr('LIQUIDATOR');
    152 35×
      address internal POSITION_MANAGER = makeAddr('POSITION_MANAGER');
    153
    154
      TokenList internal tokenList;
    155
      uint256 internal wethAssetId = 0;
    156
      uint256 internal usdxAssetId = 1;
    157
      uint256 internal daiAssetId = 2;
    158
      uint256 internal wbtcAssetId = 3;
    159
      uint256 internal usdyAssetId = 4;
    160
      uint256 internal usdzAssetId = 5;
    161
    162
      uint256 internal mintAmount_WETH = MAX_SUPPLY_AMOUNT;
    163
      uint256 internal mintAmount_USDX = MAX_SUPPLY_AMOUNT;
    164
      uint256 internal mintAmount_DAI = MAX_SUPPLY_AMOUNT;
    165
      uint256 internal mintAmount_WBTC = MAX_SUPPLY_AMOUNT;
    166
      uint256 internal mintAmount_USDY = MAX_SUPPLY_AMOUNT;
    167
      uint256 internal mintAmount_USDZ = MAX_SUPPLY_AMOUNT;
    168
    169 49×
      Decimals internal _decimals = Decimals({usdx: 6, usdy: 18, dai: 18, wbtc: 8, weth: 18, usdz: 18});
    170
    171
      bool internal deployed = false;
    172
    173
      struct Decimals {
    174
        uint8 usdx;
    175
        uint8 dai;
    176
        uint8 wbtc;
    177
        uint8 usdy;
    178
        uint8 weth;
    179
        uint8 usdz;
    180
      }
    181
    182
      struct TokenList {
    183
        WETH9 weth;
    184
        TestnetERC20 usdx;
    185
        TestnetERC20 dai;
    186
        TestnetERC20 wbtc;
    187
        TestnetERC20 usdy;
    188
        TestnetERC20 usdz;
    189
      }
    190
    191
      struct SpokeInfo {
    192
        ReserveInfo weth;
    193
        ReserveInfo wbtc;
    194
        ReserveInfo dai;
    195
        ReserveInfo usdx;
    196
        ReserveInfo usdy;
    197
        ReserveInfo usdz;
    198
        uint256 MAX_ALLOWED_ASSET_ID;
    199
      }
    200
    201
      struct ReserveInfo {
    202
        uint256 reserveId;
    203
        ISpoke.ReserveConfig reserveConfig;
    204
        ISpoke.DynamicReserveConfig dynReserveConfig;
    205
      }
    206
    207
      struct DrawnAccounting {
    208
        uint256 totalOwed;
    209
        uint256 drawn;
    210
        uint256 premium;
    211
      }
    212
    213
      // TODO: Seems this should be replaced with DrawnAccounting struct
    214
      struct Debts {
    215
        uint256 drawnDebt;
    216
        uint256 premiumDebt;
    217
        uint256 totalDebt;
    218
      }
    219
    220
      struct AssetPosition {
    221
        uint256 assetId;
    222
        uint256 addedShares;
    223
        uint256 addedAmount;
    224
        uint256 drawnShares;
    225
        uint256 drawn;
    226
        uint256 premiumShares;
    227
        int256 premiumOffsetRay;
    228
        uint256 premium;
    229
        uint40 lastUpdateTimestamp;
    230
        uint256 liquidity;
    231
        uint256 drawnIndex;
    232
        uint256 drawnRate;
    233
      }
    234
    235
      struct SpokePosition {
    236
        uint256 reserveId;
    237
        uint256 assetId;
    238
        uint256 addedShares;
    239
        uint256 addedAmount;
    240
        uint256 drawnShares;
    241
        uint256 drawn;
    242
        uint256 premiumShares;
    243
        int256 premiumOffsetRay;
    244
        uint256 premium;
    245
      }
    246
    247
      struct Reserve {
    248
        uint256 reserveId;
    249
        IHub hub;
    250
        uint16 assetId;
    251
        uint8 decimals;
    252
        uint24 dynamicConfigKey; // key of the last reserve config
    253
        bool paused;
    254
        bool frozen;
    255
        bool borrowable;
    256
        bool receiveSharesEnabled;
    257
        uint24 collateralRisk;
    258
      }
    259
    260
      mapping(ISpoke => SpokeInfo) internal spokeInfo;
    261
    262
      function setUp() public virtual {
    263
        deployFixtures();
    264
      }
    265
    266 0
      function _getProxyAdminAddress(address proxy) internal view returns (address) {
    267 0
        bytes32 slotData = vm.load(proxy, ERC1967_ADMIN_SLOT);
    268
        return address(uint160(uint256(slotData)));
    269
      }
    270
    271 0
      function _getImplementationAddress(address proxy) internal view returns (address) {
    272 0
        bytes32 slotData = vm.load(proxy, IMPLEMENTATION_SLOT);
    273
        return address(uint160(uint256(slotData)));
    274
      }
    275
    276
      function deployFixtures() internal virtual {
    277
        if (deployed) return;
    278
    279 40×
        vm.startPrank(ADMIN);
    280 41×
        accessManager = IAccessManager(address(new AccessManagerEnumerable(ADMIN)));
    281 35×
        hub1 = new Hub(address(accessManager));
    282 32×
        irStrategy = new AssetInterestRateStrategy(address(hub1));
    283 63×
        (spoke1, oracle1) = _deploySpokeWithOracle(ADMIN, address(accessManager), 'Spoke 1 (USD)');
    284 63×
        (spoke2, oracle2) = _deploySpokeWithOracle(ADMIN, address(accessManager), 'Spoke 2 (USD)');
    285 63×
        (spoke3, oracle3) = _deploySpokeWithOracle(ADMIN, address(accessManager), 'Spoke 3 (USD)');
    286 51×
        treasurySpoke = ITreasurySpoke(new TreasurySpoke(TREASURY_ADMIN, address(hub1)));
    287 39×
        vm.stopPrank();
    288
    289 41×
        vm.label(address(spoke1), 'spoke1');
    290 41×
        vm.label(address(spoke2), 'spoke2');
    291 41×
        vm.label(address(spoke3), 'spoke3');
    292
    293 20×
        setUpRoles(hub1, spoke1, accessManager);
    294 19×
        setUpRoles(hub1, spoke2, accessManager);
    295 19×
        setUpRoles(hub1, spoke3, accessManager);
    296
    297
        deployed = true; 
    298
      }
    299
    300
      function setUpRoles(IHub targetHub, ISpoke spoke, IAccessManager manager) internal virtual {
    301 40×
        vm.startPrank(ADMIN);
    302
        // Grant roles with 0 delay
    303 45×
        manager.grantRole(Roles.HUB_ADMIN_ROLE, ADMIN, 0);
    304 45×
        manager.grantRole(Roles.HUB_ADMIN_ROLE, HUB_ADMIN, 0);
    305
    306 45×
        manager.grantRole(Roles.SPOKE_ADMIN_ROLE, ADMIN, 0);
    307 45×
        manager.grantRole(Roles.SPOKE_ADMIN_ROLE, SPOKE_ADMIN, 0);
    308
    309 45×
        manager.grantRole(Roles.USER_POSITION_UPDATER_ROLE, ADMIN, 0);
    310 45×
        manager.grantRole(Roles.USER_POSITION_UPDATER_ROLE, SPOKE_ADMIN, 0);
    311 46×
        manager.grantRole(Roles.USER_POSITION_UPDATER_ROLE, USER_POSITION_UPDATER, 0);
    312
    313
        // Grant responsibilities to roles
    314
        {
    315 43×
          bytes4[] memory selectors = new bytes4[](7);
    316 27×
          selectors[0] = ISpoke.updateLiquidationConfig.selector;
    317 27×
          selectors[1] = ISpoke.addReserve.selector;
    318 27×
          selectors[2] = ISpoke.updateReserveConfig.selector;
    319 27×
          selectors[3] = ISpoke.updateDynamicReserveConfig.selector;
    320 27×
          selectors[4] = ISpoke.addDynamicReserveConfig.selector;
    321 27×
          selectors[5] = ISpoke.updatePositionManager.selector;
    322 27×
          selectors[6] = ISpoke.updateReservePriceSource.selector;
    323 46×
          manager.setTargetFunctionRole(address(spoke), selectors, Roles.SPOKE_ADMIN_ROLE);
    324
        }
    325
    326
        {
    327 43×
          bytes4[] memory selectors = new bytes4[](2);
    328 27×
          selectors[0] = ISpoke.updateUserDynamicConfig.selector;
    329 27×
          selectors[1] = ISpoke.updateUserRiskPremium.selector;
    330 46×
          manager.setTargetFunctionRole(address(spoke), selectors, Roles.USER_POSITION_UPDATER_ROLE);
    331
        }
    332
    333
        {
    334 43×
          bytes4[] memory selectors = new bytes4[](6);
    335 27×
          selectors[0] = IHub.addAsset.selector;
    336 27×
          selectors[1] = IHub.updateAssetConfig.selector;
    337 27×
          selectors[2] = IHub.addSpoke.selector;
    338 27×
          selectors[3] = IHub.updateSpokeConfig.selector;
    339 27×
          selectors[4] = IHub.setInterestRateData.selector;
    340 27×
          selectors[5] = IHub.mintFeeShares.selector;
    341 50×
          manager.setTargetFunctionRole(address(targetHub), selectors, Roles.HUB_ADMIN_ROLE);
    342
        }
    343 43×
        vm.stopPrank();
    344
      }
    345
    346
      function initEnvironment() internal {
    347
        deployMintAndApproveTokenList();
    348
        configureTokenList();
    349
      }
    350
    351
      function deployMintAndApproveTokenList() internal {
    352 152×
        tokenList = TokenList(
    353 22×
          new WETH9(),
    354 36×
          new TestnetERC20('USDX', 'USDX', _decimals.usdx),
    355 42×
          new TestnetERC20('DAI', 'DAI', _decimals.dai),
    356 42×
          new TestnetERC20('WBTC', 'WBTC', _decimals.wbtc),
    357 42×
          new TestnetERC20('USDY', 'USDY', _decimals.usdy),
    358 41×
          new TestnetERC20('USDZ', 'USDZ', _decimals.usdz)
    359
        );
    360
    361 35×
        vm.label(address(tokenList.weth), 'WETH');
    362 43×
        vm.label(address(tokenList.usdx), 'USDX');
    363 43×
        vm.label(address(tokenList.dai), 'DAI');
    364 43×
        vm.label(address(tokenList.wbtc), 'WBTC');
    365 43×
        vm.label(address(tokenList.usdy), 'USDY');
    366
    367 81×
        MAX_SUPPLY_AMOUNT_USDX = MAX_SUPPLY_ASSET_UNITS * 10 ** tokenList.usdx.decimals();
    368 81×
        MAX_SUPPLY_AMOUNT_WETH = MAX_SUPPLY_ASSET_UNITS * 10 ** tokenList.weth.decimals();
    369 81×
        MAX_SUPPLY_AMOUNT_DAI = MAX_SUPPLY_ASSET_UNITS * 10 ** tokenList.dai.decimals();
    370 81×
        MAX_SUPPLY_AMOUNT_WBTC = MAX_SUPPLY_ASSET_UNITS * 10 ** tokenList.wbtc.decimals();
    371 81×
        MAX_SUPPLY_AMOUNT_USDY = MAX_SUPPLY_ASSET_UNITS * 10 ** tokenList.usdy.decimals();
    372 81×
        MAX_SUPPLY_AMOUNT_USDZ = MAX_SUPPLY_ASSET_UNITS * 10 ** tokenList.usdz.decimals();
    373
    374 48×
        address[7] memory users = [
    375
          alice,
    376
          bob,
    377
          carol,
    378
          derl,
    379
          LIQUIDATOR,
    380
          TREASURY_ADMIN,
    381
          POSITION_MANAGER
    382
        ];
    383
    384 30×
        address[4] memory spokes = [
    385
          address(spoke1),
    386
          address(spoke2),
    387
          address(spoke3),
    388
          address(treasurySpoke)
    389
        ];
    390
    391 16×
        for (uint256 x; x < users.length; ++x) {
    392 75×
          tokenList.usdx.mint(users[x], mintAmount_USDX);
    393 75×
          tokenList.dai.mint(users[x], mintAmount_DAI);
    394 75×
          tokenList.wbtc.mint(users[x], mintAmount_WBTC);
    395 75×
          tokenList.usdy.mint(users[x], mintAmount_USDY);
    396 75×
          tokenList.usdz.mint(users[x], mintAmount_USDZ);
    397
          // deal(address(tokenList.weth), users[x], mintAmount_WETH);
    398 38×
          vm.deal(address(this), mintAmount_WETH);
    399 52×
          tokenList.weth.deposit{value: mintAmount_WETH}();
    400 78×
          tokenList.weth.transfer(users[x], mintAmount_WETH);
    401
    402 55×
          vm.startPrank(users[x]);
    403 14×
          for (uint256 y; y < spokes.length; ++y) {
    404 73×
            tokenList.weth.approve(spokes[y], UINT256_MAX);
    405 73×
            tokenList.usdx.approve(spokes[y], UINT256_MAX);
    406 73×
            tokenList.dai.approve(spokes[y], UINT256_MAX);
    407 73×
            tokenList.wbtc.approve(spokes[y], UINT256_MAX);
    408 73×
            tokenList.usdy.approve(spokes[y], UINT256_MAX);
    409 73×
            tokenList.usdz.approve(spokes[y], UINT256_MAX);
    410
          }
    411 43×
          vm.stopPrank();
    412
        }
    413
      }
    414
    415 0
      function spokeMintAndApprove() internal {
    416 0
        uint256 spokeMintAmount_USDX = 100e6 * 10 ** tokenList.usdx.decimals();
    417 0
        uint256 spokeMintAmount_DAI = 1e60;
    418 0
        uint256 spokeMintAmount_WBTC = 100e6 * 10 ** tokenList.wbtc.decimals();
    419 0
        uint256 spokeMintAmount_WETH = 100e6 * 10 ** tokenList.weth.decimals();
    420 0
        uint256 spokeMintAmount_USDY = 100e6 * 10 ** tokenList.usdy.decimals();
    421 0
        uint256 spokeMintAmount_USDZ = 100e6 * 10 ** tokenList.usdz.decimals();
    422 0
        address[3] memory spokes = [address(spoke1), address(spoke2), address(spoke3)];
    423
    424 0
        for (uint256 x; x < spokes.length; ++x) {
    425 0
          tokenList.usdx.mint(spokes[x], spokeMintAmount_USDX);
    426 0
          tokenList.dai.mint(spokes[x], spokeMintAmount_DAI);
    427 0
          tokenList.wbtc.mint(spokes[x], spokeMintAmount_WBTC);
    428 0
          tokenList.usdy.mint(spokes[x], spokeMintAmount_USDY);
    429 0
          tokenList.usdz.mint(spokes[x], spokeMintAmount_USDZ);
    430 0
          deal(address(tokenList.weth), spokes[x], spokeMintAmount_WETH);
    431
    432 0
          vm.startPrank(spokes[x]);
    433 0
          tokenList.weth.approve(address(hub1), UINT256_MAX);
    434 0
          tokenList.usdx.approve(address(hub1), UINT256_MAX);
    435 0
          tokenList.dai.approve(address(hub1), UINT256_MAX);
    436 0
          tokenList.wbtc.approve(address(hub1), UINT256_MAX);
    437 0
          tokenList.usdy.approve(address(hub1), UINT256_MAX);
    438 0
          tokenList.usdz.approve(address(hub1), UINT256_MAX);
    439 0
          vm.stopPrank();
    440
        }
    441
      }
    442
    443
      function configureTokenList() internal {
    444 41×
        IHub.SpokeConfig memory spokeConfig = IHub.SpokeConfig({
    445
          active: true,
    446
          paused: false,
    447
          addCap: Constants.MAX_ALLOWED_SPOKE_CAP,
    448
          drawCap: Constants.MAX_ALLOWED_SPOKE_CAP,
    449
          riskPremiumThreshold: Constants.MAX_ALLOWED_COLLATERAL_RISK
    450
        });
    451
    452 19×
        bytes memory encodedIrData = abi.encode(
    453 28×
          IAssetInterestRateStrategy.InterestRateData({
    454
            optimalUsageRatio: 90_00, // 90.00%
    455
            baseVariableBorrowRate: 5_00, // 5.00%
    456
            variableRateSlope1: 5_00, // 5.00%
    457
            variableRateSlope2: 5_00 // 5.00%
    458
          })
    459
        );
    460
    461
        // Add all assets to the Hub
    462 36×
        vm.startPrank(ADMIN);
    463
        // add WETH
    464 71×
        hub1.addAsset(
    465 10×
          address(tokenList.weth),
    466 59×
          tokenList.weth.decimals(),
    467
          address(treasurySpoke),
    468
          address(irStrategy),
    469
          encodedIrData
    470
        );
    471 45×
        hub1.updateAssetConfig(
    472
          wethAssetId,
    473 29×
          IHub.AssetConfig({
    474
            liquidityFee: 10_00,
    475
            feeReceiver: address(treasurySpoke),
    476
            irStrategy: address(irStrategy),
    477
            reinvestmentController: address(0)
    478
          }),
    479 12×
          new bytes(0)
    480
        );
    481
        // add USDX
    482 71×
        hub1.addAsset(
    483 10×
          address(tokenList.usdx),
    484 59×
          tokenList.usdx.decimals(),
    485
          address(treasurySpoke),
    486
          address(irStrategy),
    487
          encodedIrData
    488
        );
    489 45×
        hub1.updateAssetConfig(
    490
          usdxAssetId,
    491 29×
          IHub.AssetConfig({
    492
            liquidityFee: 5_00,
    493
            feeReceiver: address(treasurySpoke),
    494
            irStrategy: address(irStrategy),
    495
            reinvestmentController: address(0)
    496
          }),
    497 12×
          new bytes(0)
    498
        );
    499
        // add DAI
    500 71×
        hub1.addAsset(
    501 10×
          address(tokenList.dai),
    502 59×
          tokenList.dai.decimals(),
    503
          address(treasurySpoke),
    504
          address(irStrategy),
    505
          encodedIrData
    506
        );
    507 45×
        hub1.updateAssetConfig(
    508
          daiAssetId,
    509 29×
          IHub.AssetConfig({
    510
            liquidityFee: 5_00,
    511
            feeReceiver: address(treasurySpoke),
    512
            irStrategy: address(irStrategy),
    513
            reinvestmentController: address(0)
    514
          }),
    515 12×
          new bytes(0)
    516
        );
    517
        // add WBTC
    518 71×
        hub1.addAsset(
    519 10×
          address(tokenList.wbtc),
    520 59×
          tokenList.wbtc.decimals(),
    521
          address(treasurySpoke),
    522
          address(irStrategy),
    523
          encodedIrData
    524
        );
    525 45×
        hub1.updateAssetConfig(
    526
          wbtcAssetId,
    527 29×
          IHub.AssetConfig({
    528
            liquidityFee: 10_00,
    529
            feeReceiver: address(treasurySpoke),
    530
            irStrategy: address(irStrategy),
    531
            reinvestmentController: address(0)
    532
          }),
    533 12×
          new bytes(0)
    534
        );
    535
        // add USDY
    536 71×
        hub1.addAsset(
    537 11×
          address(tokenList.usdy),
    538 58×
          tokenList.usdy.decimals(),
    539
          address(treasurySpoke),
    540
          address(irStrategy),
    541
          encodedIrData
    542
        );
    543 45×
        hub1.updateAssetConfig(
    544
          usdyAssetId,
    545 29×
          IHub.AssetConfig({
    546
            liquidityFee: 10_00,
    547
            feeReceiver: address(treasurySpoke),
    548
            irStrategy: address(irStrategy),
    549
            reinvestmentController: address(0)
    550
          }),
    551 12×
          new bytes(0)
    552
        );
    553
        // add USDZ
    554 71×
        hub1.addAsset(
    555 10×
          address(tokenList.usdz),
    556 59×
          tokenList.usdz.decimals(),
    557
          address(treasurySpoke),
    558
          address(irStrategy),
    559
          encodedIrData
    560
        );
    561 56×
        hub1.updateAssetConfig(
    562 65×
          hub1.getAssetCount() - 1,
    563 28×
          IHub.AssetConfig({
    564
            liquidityFee: 5_00,
    565
            feeReceiver: address(treasurySpoke),
    566
            irStrategy: address(irStrategy),
    567
            reinvestmentController: address(0)
    568
          }),
    569 12×
          new bytes(0)
    570
        );
    571
    572
        // Liquidation configs
    573 41×
        spoke1.updateLiquidationConfig(
    574 22×
          ISpoke.LiquidationConfig({
    575
            targetHealthFactor: 1.05e18,
    576
            healthFactorForMaxBonus: 0.7e18,
    577
            liquidationBonusFactor: 20_00
    578
          })
    579
        );
    580 41×
        spoke2.updateLiquidationConfig(
    581 22×
          ISpoke.LiquidationConfig({
    582
            targetHealthFactor: 1.04e18,
    583
            healthFactorForMaxBonus: 0.8e18,
    584
            liquidationBonusFactor: 15_00
    585
          })
    586
        );
    587 45×
        spoke3.updateLiquidationConfig(
    588 22×
          ISpoke.LiquidationConfig({
    589
            targetHealthFactor: 1.03e18,
    590
            healthFactorForMaxBonus: 0.9e18,
    591
            liquidationBonusFactor: 10_00
    592
          })
    593
        );
    594
    595
        // Spoke 1 reserve configs
    596 130×
        spokeInfo[spoke1].weth.reserveConfig = _getDefaultReserveConfig(15_00);
    597 77×
        spokeInfo[spoke1].weth.dynReserveConfig = ISpoke.DynamicReserveConfig({
    598
          collateralFactor: 80_00,
    599
          maxLiquidationBonus: 105_00,
    600
          liquidationFee: 10_00
    601
        });
    602 126×
        spokeInfo[spoke1].wbtc.reserveConfig = _getDefaultReserveConfig(15_00);
    603 77×
        spokeInfo[spoke1].wbtc.dynReserveConfig = ISpoke.DynamicReserveConfig({
    604
          collateralFactor: 75_00,
    605
          maxLiquidationBonus: 103_00,
    606
          liquidationFee: 15_00
    607
        });
    608 125×
        spokeInfo[spoke1].dai.reserveConfig = _getDefaultReserveConfig(20_00);
    609 77×
        spokeInfo[spoke1].dai.dynReserveConfig = ISpoke.DynamicReserveConfig({
    610
          collateralFactor: 78_00,
    611
          maxLiquidationBonus: 102_00,
    612
          liquidationFee: 10_00
    613
        });
    614 125×
        spokeInfo[spoke1].usdx.reserveConfig = _getDefaultReserveConfig(50_00);
    615 77×
        spokeInfo[spoke1].usdx.dynReserveConfig = ISpoke.DynamicReserveConfig({
    616
          collateralFactor: 78_00,
    617
          maxLiquidationBonus: 101_00,
    618
          liquidationFee: 12_00
    619
        });
    620 125×
        spokeInfo[spoke1].usdy.reserveConfig = _getDefaultReserveConfig(50_00);
    621 77×
        spokeInfo[spoke1].usdy.dynReserveConfig = ISpoke.DynamicReserveConfig({
    622
          collateralFactor: 78_00,
    623
          maxLiquidationBonus: 101_50,
    624
          liquidationFee: 15_00
    625
        });
    626
    627 92×
        spokeInfo[spoke1].weth.reserveId = spoke1.addReserve(
    628
          address(hub1),
    629
          wethAssetId,
    630
          _deployMockPriceFeed(spoke1, 2000e8),
    631 19×
          spokeInfo[spoke1].weth.reserveConfig,
    632
          spokeInfo[spoke1].weth.dynReserveConfig
    633
        );
    634 93×
        spokeInfo[spoke1].wbtc.reserveId = spoke1.addReserve(
    635
          address(hub1),
    636
          wbtcAssetId,
    637
          _deployMockPriceFeed(spoke1, 50_000e8),
    638 20×
          spokeInfo[spoke1].wbtc.reserveConfig,
    639
          spokeInfo[spoke1].wbtc.dynReserveConfig
    640
        );
    641 94×
        spokeInfo[spoke1].dai.reserveId = spoke1.addReserve(
    642
          address(hub1),
    643
          daiAssetId,
    644
          _deployMockPriceFeed(spoke1, 1e8),
    645 19×
          spokeInfo[spoke1].dai.reserveConfig,
    646
          spokeInfo[spoke1].dai.dynReserveConfig
    647
        );
    648 94×
        spokeInfo[spoke1].usdx.reserveId = spoke1.addReserve(
    649
          address(hub1),
    650
          usdxAssetId,
    651
          _deployMockPriceFeed(spoke1, 1e8),
    652 19×
          spokeInfo[spoke1].usdx.reserveConfig,
    653
          spokeInfo[spoke1].usdx.dynReserveConfig
    654
        );
    655 96×
        spokeInfo[spoke1].usdy.reserveId = spoke1.addReserve(
    656
          address(hub1),
    657
          usdyAssetId,
    658
          _deployMockPriceFeed(spoke1, 1e8),
    659 19×
          spokeInfo[spoke1].usdy.reserveConfig,
    660
          spokeInfo[spoke1].usdy.dynReserveConfig
    661
        );
    662
    663 56×
        hub1.addSpoke(wethAssetId, address(spoke1), spokeConfig);
    664 55×
        hub1.addSpoke(wbtcAssetId, address(spoke1), spokeConfig);
    665 55×
        hub1.addSpoke(daiAssetId, address(spoke1), spokeConfig);
    666 55×
        hub1.addSpoke(usdxAssetId, address(spoke1), spokeConfig);
    667 59×
        hub1.addSpoke(usdyAssetId, address(spoke1), spokeConfig);
    668
    669
        // Spoke 2 reserve configs
    670 130×
        spokeInfo[spoke2].wbtc.reserveConfig = _getDefaultReserveConfig(0);
    671 77×
        spokeInfo[spoke2].wbtc.dynReserveConfig = ISpoke.DynamicReserveConfig({
    672
          collateralFactor: 80_00,
    673
          maxLiquidationBonus: 105_00,
    674
          liquidationFee: 10_00
    675
        });
    676 126×
        spokeInfo[spoke2].weth.reserveConfig = _getDefaultReserveConfig(10_00);
    677 77×
        spokeInfo[spoke2].weth.dynReserveConfig = ISpoke.DynamicReserveConfig({
    678
          collateralFactor: 76_00,
    679
          maxLiquidationBonus: 103_00,
    680
          liquidationFee: 15_00
    681
        });
    682 125×
        spokeInfo[spoke2].dai.reserveConfig = _getDefaultReserveConfig(20_00);
    683 77×
        spokeInfo[spoke2].dai.dynReserveConfig = ISpoke.DynamicReserveConfig({
    684
          collateralFactor: 72_00,
    685
          maxLiquidationBonus: 102_00,
    686
          liquidationFee: 10_00
    687
        });
    688 125×
        spokeInfo[spoke2].usdx.reserveConfig = _getDefaultReserveConfig(50_00);
    689 77×
        spokeInfo[spoke2].usdx.dynReserveConfig = ISpoke.DynamicReserveConfig({
    690
          collateralFactor: 72_00,
    691
          maxLiquidationBonus: 101_00,
    692
          liquidationFee: 12_00
    693
        });
    694 125×
        spokeInfo[spoke2].usdy.reserveConfig = _getDefaultReserveConfig(50_00);
    695 77×
        spokeInfo[spoke2].usdy.dynReserveConfig = ISpoke.DynamicReserveConfig({
    696
          collateralFactor: 72_00,
    697
          maxLiquidationBonus: 101_50,
    698
          liquidationFee: 15_00
    699
        });
    700 125×
        spokeInfo[spoke2].usdz.reserveConfig = _getDefaultReserveConfig(100_00);
    701 77×
        spokeInfo[spoke2].usdz.dynReserveConfig = ISpoke.DynamicReserveConfig({
    702
          collateralFactor: 70_00,
    703
          maxLiquidationBonus: 106_00,
    704
          liquidationFee: 10_00
    705
        });
    706
    707 93×
        spokeInfo[spoke2].wbtc.reserveId = spoke2.addReserve(
    708
          address(hub1),
    709
          wbtcAssetId,
    710
          _deployMockPriceFeed(spoke2, 50_000e8),
    711 20×
          spokeInfo[spoke2].wbtc.reserveConfig,
    712
          spokeInfo[spoke2].wbtc.dynReserveConfig
    713
        );
    714 92×
        spokeInfo[spoke2].weth.reserveId = spoke2.addReserve(
    715
          address(hub1),
    716
          wethAssetId,
    717
          _deployMockPriceFeed(spoke2, 2000e8),
    718 19×
          spokeInfo[spoke2].weth.reserveConfig,
    719
          spokeInfo[spoke2].weth.dynReserveConfig
    720
        );
    721 94×
        spokeInfo[spoke2].dai.reserveId = spoke2.addReserve(
    722
          address(hub1),
    723
          daiAssetId,
    724
          _deployMockPriceFeed(spoke2, 1e8),
    725 19×
          spokeInfo[spoke2].dai.reserveConfig,
    726
          spokeInfo[spoke2].dai.dynReserveConfig
    727
        );
    728 94×
        spokeInfo[spoke2].usdx.reserveId = spoke2.addReserve(
    729
          address(hub1),
    730
          usdxAssetId,
    731
          _deployMockPriceFeed(spoke2, 1e8),
    732 19×
          spokeInfo[spoke2].usdx.reserveConfig,
    733
          spokeInfo[spoke2].usdx.dynReserveConfig
    734
        );
    735 94×
        spokeInfo[spoke2].usdy.reserveId = spoke2.addReserve(
    736
          address(hub1),
    737
          usdyAssetId,
    738
          _deployMockPriceFeed(spoke2, 1e8),
    739 19×
          spokeInfo[spoke2].usdy.reserveConfig,
    740
          spokeInfo[spoke2].usdy.dynReserveConfig
    741
        );
    742 96×
        spokeInfo[spoke2].usdz.reserveId = spoke2.addReserve(
    743
          address(hub1),
    744
          usdzAssetId,
    745
          _deployMockPriceFeed(spoke2, 1e8),
    746 19×
          spokeInfo[spoke2].usdz.reserveConfig,
    747
          spokeInfo[spoke2].usdz.dynReserveConfig
    748
        );
    749
    750 56×
        hub1.addSpoke(wbtcAssetId, address(spoke2), spokeConfig);
    751 55×
        hub1.addSpoke(wethAssetId, address(spoke2), spokeConfig);
    752 55×
        hub1.addSpoke(daiAssetId, address(spoke2), spokeConfig);
    753 55×
        hub1.addSpoke(usdxAssetId, address(spoke2), spokeConfig);
    754 55×
        hub1.addSpoke(usdyAssetId, address(spoke2), spokeConfig);
    755 59×
        hub1.addSpoke(usdzAssetId, address(spoke2), spokeConfig);
    756
    757
        // Spoke 3 reserve configs
    758 129×
        spokeInfo[spoke3].dai.reserveConfig = _getDefaultReserveConfig(0);
    759 77×
        spokeInfo[spoke3].dai.dynReserveConfig = ISpoke.DynamicReserveConfig({
    760
          collateralFactor: 75_00,
    761
          maxLiquidationBonus: 104_00,
    762
          liquidationFee: 11_00
    763
        });
    764 125×
        spokeInfo[spoke3].usdx.reserveConfig = _getDefaultReserveConfig(10_00);
    765 77×
        spokeInfo[spoke3].usdx.dynReserveConfig = ISpoke.DynamicReserveConfig({
    766
          collateralFactor: 75_00,
    767
          maxLiquidationBonus: 103_00,
    768
          liquidationFee: 15_00
    769
        });
    770 126×
        spokeInfo[spoke3].weth.reserveConfig = _getDefaultReserveConfig(20_00);
    771 77×
        spokeInfo[spoke3].weth.dynReserveConfig = ISpoke.DynamicReserveConfig({
    772
          collateralFactor: 79_00,
    773
          maxLiquidationBonus: 102_00,
    774
          liquidationFee: 10_00
    775
        });
    776 126×
        spokeInfo[spoke3].wbtc.reserveConfig = _getDefaultReserveConfig(50_00);
    777 77×
        spokeInfo[spoke3].wbtc.dynReserveConfig = ISpoke.DynamicReserveConfig({
    778
          collateralFactor: 77_00,
    779
          maxLiquidationBonus: 101_00,
    780
          liquidationFee: 12_00
    781
        });
    782
    783 94×
        spokeInfo[spoke3].dai.reserveId = spoke3.addReserve(
    784
          address(hub1),
    785
          daiAssetId,
    786
          _deployMockPriceFeed(spoke3, 1e8),
    787 19×
          spokeInfo[spoke3].dai.reserveConfig,
    788
          spokeInfo[spoke3].dai.dynReserveConfig
    789
        );
    790 94×
        spokeInfo[spoke3].usdx.reserveId = spoke3.addReserve(
    791
          address(hub1),
    792
          usdxAssetId,
    793
          _deployMockPriceFeed(spoke3, 1e8),
    794 19×
          spokeInfo[spoke3].usdx.reserveConfig,
    795
          spokeInfo[spoke3].usdx.dynReserveConfig
    796
        );
    797 92×
        spokeInfo[spoke3].weth.reserveId = spoke3.addReserve(
    798
          address(hub1),
    799
          wethAssetId,
    800
          _deployMockPriceFeed(spoke3, 2000e8),
    801 19×
          spokeInfo[spoke3].weth.reserveConfig,
    802
          spokeInfo[spoke3].weth.dynReserveConfig
    803
        );
    804 95×
        spokeInfo[spoke3].wbtc.reserveId = spoke3.addReserve(
    805
          address(hub1),
    806
          wbtcAssetId,
    807
          _deployMockPriceFeed(spoke3, 50_000e8),
    808 20×
          spokeInfo[spoke3].wbtc.reserveConfig,
    809
          spokeInfo[spoke3].wbtc.dynReserveConfig
    810
        );
    811
    812 56×
        hub1.addSpoke(daiAssetId, address(spoke3), spokeConfig);
    813 55×
        hub1.addSpoke(usdxAssetId, address(spoke3), spokeConfig);
    814 55×
        hub1.addSpoke(wethAssetId, address(spoke3), spokeConfig);
    815 59×
        hub1.addSpoke(wbtcAssetId, address(spoke3), spokeConfig);
    816
    817 43×
        vm.stopPrank();
    818
      }
    819
    820
      /* @dev Configures Hub 2 with the following assetIds:
    821
       * 0: WETH
    822
       * 1: USDX
    823
       * 2: DAI
    824
       * 3: WBTC
    825
       */
    826 0
      function hub2Fixture() internal returns (IHub, AssetInterestRateStrategy) {
    827 0
        IAccessManager accessManager2 = IAccessManager(address(new AccessManagerEnumerable(ADMIN)));
    828 0
        IHub hub2 = new Hub(address(accessManager2));
    829 0
        vm.label(address(hub2), 'Hub2');
    830 0
        AssetInterestRateStrategy hub2IrStrategy = new AssetInterestRateStrategy(address(hub2));
    831
    832
        // Configure IR Strategy for hub 2
    833 0
        bytes memory encodedIrData = abi.encode(
    834 0
          IAssetInterestRateStrategy.InterestRateData({
    835 0
            optimalUsageRatio: 90_00, // 90.00%
    836 0
            baseVariableBorrowRate: 5_00, // 5.00%
    837
            variableRateSlope1: 5_00, // 5.00%
    838
            variableRateSlope2: 5_00 // 5.00%
    839
          })
    840
        );
    841
    842 0
        vm.startPrank(ADMIN);
    843
    844
        // Add assets to the second hub
    845
        // Add WETH
    846 0
        hub2.addAsset(
    847 0
          address(tokenList.weth),
    848 0
          tokenList.weth.decimals(),
    849 0
          address(treasurySpoke),
    850 0
          address(hub2IrStrategy),
    851 0
          encodedIrData
    852
        );
    853
    854
        // Add USDX
    855 0
        hub2.addAsset(
    856 0
          address(tokenList.usdx),
    857 0
          tokenList.usdx.decimals(),
    858 0
          address(treasurySpoke),
    859 0
          address(hub2IrStrategy),
    860 0
          encodedIrData
    861
        );
    862
    863
        // Add DAI
    864 0
        hub2.addAsset(
    865 0
          address(tokenList.dai),
    866 0
          tokenList.dai.decimals(),
    867 0
          address(treasurySpoke),
    868 0
          address(hub2IrStrategy),
    869 0
          encodedIrData
    870
        );
    871
    872
        // Add WBTC
    873 0
        hub2.addAsset(
    874 0
          address(tokenList.wbtc),
    875 0
          tokenList.wbtc.decimals(),
    876 0
          address(treasurySpoke),
    877 0
          address(hub2IrStrategy),
    878 0
          encodedIrData
    879
        );
    880 0
        vm.stopPrank();
    881
    882 0
        setUpRoles(hub2, spoke1, accessManager2);
    883
    884 0
        return (hub2, hub2IrStrategy);
    885
      }
    886
    887
      /* @dev Configures Hub 3 with the following assetIds:
    888
       * 0: DAI
    889
       * 1: USDX
    890
       * 2: WBTC
    891
       * 3: WETH
    892
       */
    893 0
      function hub3Fixture() internal returns (IHub, AssetInterestRateStrategy) {
    894 0
        IAccessManager accessManager3 = IAccessManager(address(new AccessManagerEnumerable(ADMIN)));
    895 0
        IHub hub3 = new Hub(address(accessManager3));
    896 0
        AssetInterestRateStrategy hub3IrStrategy = new AssetInterestRateStrategy(address(hub3));
    897
    898
        // Configure IR Strategy for hub 3
    899 0
        bytes memory encodedIrData = abi.encode(
    900 0
          IAssetInterestRateStrategy.InterestRateData({
    901 0
            optimalUsageRatio: 90_00, // 90.00%
    902 0
            baseVariableBorrowRate: 5_00, // 5.00%
    903
            variableRateSlope1: 5_00, // 5.00%
    904
            variableRateSlope2: 5_00 // 5.00%
    905
          })
    906
        );
    907
    908 0
        vm.startPrank(ADMIN);
    909
        // Add DAI
    910 0
        hub3.addAsset(
    911 0
          address(tokenList.dai),
    912 0
          tokenList.dai.decimals(),
    913 0
          address(treasurySpoke),
    914 0
          address(hub3IrStrategy),
    915 0
          encodedIrData
    916
        );
    917
    918
        // Add USDX
    919 0
        hub3.addAsset(
    920 0
          address(tokenList.usdx),
    921 0
          tokenList.usdx.decimals(),
    922 0
          address(treasurySpoke),
    923 0
          address(hub3IrStrategy),
    924 0
          encodedIrData
    925
        );
    926
    927
        // Add WBTC
    928 0
        hub3.addAsset(
    929 0
          address(tokenList.wbtc),
    930 0
          tokenList.wbtc.decimals(),
    931 0
          address(treasurySpoke),
    932 0
          address(hub3IrStrategy),
    933 0
          encodedIrData
    934
        );
    935
    936
        // Add WETH
    937 0
        hub3.addAsset(
    938 0
          address(tokenList.weth),
    939 0
          tokenList.weth.decimals(),
    940
          address(treasurySpoke),
    941
          address(hub3IrStrategy),
    942
          encodedIrData
    943
        );
    944
    945
        vm.stopPrank();
    946
    947
        setUpRoles(hub3, spoke1, accessManager3);
    948
    949
        return (hub3, hub3IrStrategy);
    950
      }
    951
    952
      function updateAssetFeeReceiver(
    953
        IHub hub,
    954
        uint256 assetId,
    955
        address newFeeReceiver
    956
      ) internal pausePrank {
    957
        IHub.AssetConfig memory config = hub.getAssetConfig(assetId);
    958
        config.feeReceiver = newFeeReceiver;
    959
    960
        vm.prank(HUB_ADMIN);
    961
        hub.updateAssetConfig(assetId, config, new bytes(0));
    962
    963
        assertEq(hub.getAssetConfig(assetId), config);
    964
      }
    965
    966 0
      function updateAssetReinvestmentController(
    967
        IHub hub,
    968
        uint256 assetId,
    969
        address newReinvestmentController
    970
      ) internal pausePrank {
    971 0
        IHub.AssetConfig memory config = hub.getAssetConfig(assetId);
    972 0
        config.reinvestmentController = newReinvestmentController;
    973
    974 0
        vm.prank(HUB_ADMIN);
    975 0
        hub.updateAssetConfig(assetId, config, new bytes(0));
    976
    977 0
        assertEq(hub.getAssetConfig(assetId), config);
    978
      }
    979
    980 0
      function updateReserveFrozenFlag(
    981
        ISpoke spoke,
    982
        uint256 reserveId,
    983
        bool newFrozenFlag
    984
      ) internal pausePrank {
    985 0
        ISpoke.ReserveConfig memory config = spoke.getReserveConfig(reserveId);
    986 0
        config.frozen = newFrozenFlag;
    987
    988 0
        vm.prank(SPOKE_ADMIN);
    989
        spoke.updateReserveConfig(reserveId, config);
    990
    991
        assertEq(spoke.getReserveConfig(reserveId), config);
    992
      }
    993
    994 0
      function _updateReservePausedFlag(
    995
        ISpoke spoke,
    996
        uint256 reserveId,
    997
        bool paused
    998
      ) internal pausePrank {
    999 0
        ISpoke.ReserveConfig memory config = spoke.getReserveConfig(reserveId);
    1000 0
        config.paused = paused;
    1001
    1002 0
        vm.prank(SPOKE_ADMIN);
    1003 0
        spoke.updateReserveConfig(reserveId, config);
    1004
    1005 0
        assertEq(spoke.getReserveConfig(reserveId), config);
    1006
      }
    1007
    1008 0
      function _updateReserveReceiveSharesEnabledFlag(
    1009
        ISpoke spoke,
    1010
        uint256 reserveId,
    1011
        bool receiveSharesEnabled
    1012
      ) internal pausePrank {
    1013 0
        ISpoke.ReserveConfig memory config = spoke.getReserveConfig(reserveId);
    1014 0
        config.receiveSharesEnabled = receiveSharesEnabled;
    1015
    1016 0
        vm.prank(SPOKE_ADMIN);
    1017 0
        spoke.updateReserveConfig(reserveId, config);
    1018
    1019 0
        assertEq(spoke.getReserveConfig(reserveId), config);
    1020
      }
    1021
    1022 0
      function _updateLiquidationConfig(
    1023
        ISpoke spoke,
    1024
        ISpoke.LiquidationConfig memory config
    1025
      ) internal pausePrank {
    1026 0
        vm.prank(SPOKE_ADMIN);
    1027 0
        spoke.updateLiquidationConfig(config);
    1028
    1029 0
        assertEq(spoke.getLiquidationConfig(), config);
    1030
      }
    1031
    1032 0
      function _updateMaxLiquidationBonus(
    1033
        ISpoke spoke,
    1034
        uint256 reserveId,
    1035
        uint32 newMaxLiquidationBonus
    1036 0
      ) internal pausePrank returns (uint24) {
    1037 0
        ISpoke.DynamicReserveConfig memory config = _getLatestDynamicReserveConfig(spoke, reserveId);
    1038 0
        config.maxLiquidationBonus = newMaxLiquidationBonus;
    1039
    1040 0
        vm.prank(SPOKE_ADMIN);
    1041 0
        uint24 dynamicConfigKey = spoke.addDynamicReserveConfig(reserveId, config);
    1042
    1043 0
        assertEq(_getLatestDynamicReserveConfig(spoke, reserveId), config);
    1044 0
        return dynamicConfigKey;
    1045
      }
    1046
    1047 0
      function _updateLiquidationFee(
    1048
        ISpoke spoke,
    1049
        uint256 reserveId,
    1050
        uint16 newLiquidationFee
    1051 0
      ) internal pausePrank returns (uint24) {
    1052 0
        ISpoke.DynamicReserveConfig memory config = _getLatestDynamicReserveConfig(spoke, reserveId);
    1053 0
        config.liquidationFee = newLiquidationFee;
    1054
    1055 0
        vm.prank(SPOKE_ADMIN);
    1056 0
        uint24 dynamicConfigKey = spoke.addDynamicReserveConfig(reserveId, config);
    1057
    1058 0
        assertEq(_getLatestDynamicReserveConfig(spoke, reserveId), config);
    1059 0
        return dynamicConfigKey;
    1060
      }
    1061
    1062 0
      function _updateCollateralFactorAndLiquidationBonus(
    1063
        ISpoke spoke,
    1064
        uint256 reserveId,
    1065
        uint256 newCollateralFactor,
    1066
        uint256 newLiquidationBonus
    1067 0
      ) internal pausePrank returns (uint24) {
    1068 0
        ISpoke.DynamicReserveConfig memory config = _getLatestDynamicReserveConfig(spoke, reserveId);
    1069 0
        config.collateralFactor = newCollateralFactor.toUint16();
    1070 0
        config.maxLiquidationBonus = newLiquidationBonus.toUint32();
    1071
    1072 0
        vm.prank(SPOKE_ADMIN);
    1073 0
        uint24 dynamicConfigKey = spoke.addDynamicReserveConfig(reserveId, config);
    1074
    1075 0
        assertEq(_getLatestDynamicReserveConfig(spoke, reserveId), config);
    1076 0
        return dynamicConfigKey;
    1077
      }
    1078
    1079 0
      function _updateCollateralFactor(
    1080
        ISpoke spoke,
    1081
        uint256 reserveId,
    1082
        uint256 newCollateralFactor
    1083 0
      ) internal pausePrank returns (uint24) {
    1084 0
        ISpoke.DynamicReserveConfig memory config = _getLatestDynamicReserveConfig(spoke, reserveId);
    1085 0
        config.collateralFactor = newCollateralFactor.toUint16();
    1086 0
        vm.prank(SPOKE_ADMIN);
    1087 0
        uint24 dynamicConfigKey = spoke.addDynamicReserveConfig(reserveId, config);
    1088
    1089 0
        assertEq(_getLatestDynamicReserveConfig(spoke, reserveId), config);
    1090 0
        return dynamicConfigKey;
    1091
      }
    1092
    1093
      function _updateCollateralFactorAtKey(
    1094
        ISpoke spoke,
    1095
        uint256 reserveId,
    1096
        uint24 dynamicConfigKey,
    1097
        uint256 newCollateralFactor
    1098
      ) internal pausePrank {
    1099
        ISpoke.DynamicReserveConfig memory config = spoke.getDynamicReserveConfig(
    1100
          reserveId,
    1101
          dynamicConfigKey
    1102
        );
    1103
        config.collateralFactor = newCollateralFactor.toUint16();
    1104
        vm.prank(SPOKE_ADMIN);
    1105
        spoke.updateDynamicReserveConfig(reserveId, dynamicConfigKey, config);
    1106
    1107
        assertEq(_getLatestDynamicReserveConfig(spoke, reserveId), config);
    1108
      }
    1109
    1110 0
      function updateReserveBorrowableFlag(
    1111
        ISpoke spoke,
    1112
        uint256 reserveId,
    1113
        bool newBorrowable
    1114
      ) internal pausePrank {
    1115 0
        ISpoke.ReserveConfig memory config = spoke.getReserveConfig(reserveId);
    1116 0
        config.borrowable = newBorrowable;
    1117 0
        vm.prank(SPOKE_ADMIN);
    1118
        spoke.updateReserveConfig(reserveId, config);
    1119
    1120
        assertEq(spoke.getReserveConfig(reserveId), config);
    1121
      }
    1122
    1123 0
      function _updateCollateralRisk(
    1124
        ISpoke spoke,
    1125
        uint256 reserveId,
    1126
        uint24 newCollateralRisk
    1127
      ) internal pausePrank {
    1128 0
        ISpoke.ReserveConfig memory config = spoke.getReserveConfig(reserveId);
    1129 0
        config.collateralRisk = newCollateralRisk;
    1130 0
        vm.prank(SPOKE_ADMIN);
    1131 0
        spoke.updateReserveConfig(reserveId, config);
    1132
    1133 0
        assertEq(spoke.getReserveConfig(reserveId), config);
    1134
      }
    1135
    1136 0
      function updateLiquidityFee(IHub hub, uint256 assetId, uint256 liquidityFee) internal pausePrank {
    1137 0
        IHub.AssetConfig memory config = hub.getAssetConfig(assetId);
    1138 0
        config.liquidityFee = liquidityFee.toUint16();
    1139 0
        vm.prank(HUB_ADMIN);
    1140 0
        hub.updateAssetConfig(assetId, config, new bytes(0));
    1141
    1142 0
        assertEq(hub.getAssetConfig(assetId), config);
    1143
      }
    1144
    1145 0
      function _updateTargetHealthFactor(
    1146
        ISpoke spoke,
    1147
        uint128 newTargetHealthFactor
    1148
      ) internal pausePrank {
    1149 0
        ISpoke.LiquidationConfig memory liqConfig = spoke.getLiquidationConfig();
    1150 0
        liqConfig.targetHealthFactor = newTargetHealthFactor;
    1151 0
        vm.prank(SPOKE_ADMIN);
    1152 0
        spoke.updateLiquidationConfig(liqConfig);
    1153
    1154 0
        assertEq(spoke.getLiquidationConfig(), liqConfig);
    1155
      }
    1156
    1157
      function getTargetHealthFactor(ISpoke spoke) internal view returns (uint256) {
    1158
        ISpoke.LiquidationConfig memory liqConfig = spoke.getLiquidationConfig();
    1159
        return liqConfig.targetHealthFactor;
    1160
      }
    1161
    1162
      /// @dev pseudo random randomizer
    1163 0
      function randomizer(uint256 min, uint256 max) internal returns (uint256) {
    1164 0
        return vm.randomUint(min, max);
    1165
      }
    1166
    1167 0
      function _randomNonceKey() internal returns (uint192) {
    1168 0
        return uint192(vm.randomUint());
    1169
      }
    1170
    1171
      function _randomNonce() internal returns (uint64) {
    1172
        return uint64(vm.randomUint());
    1173
      }
    1174
    1175
      // assumes spoke has usdx supported
    1176 0
      function _usdxReserveId(ISpoke spoke) internal view returns (uint256) {
    1177 0
        return spokeInfo[spoke].usdx.reserveId;
    1178
      }
    1179
    1180
      // assumes spoke has usdy supported
    1181 0
      function _usdyReserveId(ISpoke spoke) internal view returns (uint256) {
    1182 0
        return spokeInfo[spoke].usdy.reserveId;
    1183
      }
    1184
    1185
      // assumes spoke has dai supported
    1186 0
      function _daiReserveId(ISpoke spoke) internal view returns (uint256) {
    1187 0
        return spokeInfo[spoke].dai.reserveId;
    1188
      }
    1189
    1190
      // assumes spoke has weth supported
    1191 0
      function _wethReserveId(ISpoke spoke) internal view returns (uint256) {
    1192 0
        return spokeInfo[spoke].weth.reserveId;
    1193
      }
    1194
    1195
      // assumes spoke has wbtc supported
    1196 0
      function _wbtcReserveId(ISpoke spoke) internal view returns (uint256) {
    1197 0
        return spokeInfo[spoke].wbtc.reserveId;
    1198
      }
    1199
    1200
      // assumes spoke has usdz supported
    1201 0
      function _usdzReserveId(ISpoke spoke) internal view returns (uint256) {
    1202 0
        return spokeInfo[spoke].usdz.reserveId;
    1203
      }
    1204
    1205 0
      function _updateSpokePaused(
    1206
        IHub hub,
    1207
        uint256 assetId,
    1208
        address spoke,
    1209
        bool paused
    1210
      ) internal pausePrank {
    1211 0
        IHub.SpokeConfig memory spokeConfig = hub.getSpokeConfig(assetId, spoke);
    1212 0
        spokeConfig.paused = paused;
    1213 0
        vm.prank(HUB_ADMIN);
    1214 0
        hub.updateSpokeConfig(assetId, spoke, spokeConfig);
    1215
    1216 0
        assertEq(hub.getSpokeConfig(assetId, spoke), spokeConfig);
    1217
      }
    1218
    1219 0
      function updateSpokeActive(
    1220
        IHub hub,
    1221
        uint256 assetId,
    1222
        address spoke,
    1223
        bool newActive
    1224
      ) internal pausePrank {
    1225 0
        IHub.SpokeConfig memory spokeConfig = hub.getSpokeConfig(assetId, spoke);
    1226 0
        spokeConfig.active = newActive;
    1227 0
        vm.prank(HUB_ADMIN);
    1228 0
        hub.updateSpokeConfig(assetId, spoke, spokeConfig);
    1229
    1230 0
        assertEq(hub.getSpokeConfig(assetId, spoke), spokeConfig);
    1231
      }
    1232
    1233 0
      function updateDrawCap(
    1234
        IHub hub,
    1235
        uint256 assetId,
    1236
        address spoke,
    1237
        uint40 newDrawCap
    1238
      ) internal pausePrank {
    1239 0
        IHub.SpokeConfig memory spokeConfig = hub.getSpokeConfig(assetId, spoke);
    1240 0
        spokeConfig.drawCap = newDrawCap;
    1241 0
        vm.prank(HUB_ADMIN);
    1242 0
        hub.updateSpokeConfig(assetId, spoke, spokeConfig);
    1243
    1244 0
        assertEq(hub.getSpokeConfig(assetId, spoke), spokeConfig);
    1245
      }
    1246
    1247 0
      function _updateSpokeRiskPremiumThreshold(
    1248
        IHub hub,
    1249
        uint256 assetId,
    1250
        address spoke,
    1251
        uint24 newRiskPremiumThreshold
    1252
      ) internal pausePrank {
    1253 0
        IHub.SpokeConfig memory spokeConfig = hub.getSpokeConfig(assetId, spoke);
    1254 0
        spokeConfig.riskPremiumThreshold = newRiskPremiumThreshold;
    1255 0
        vm.prank(HUB_ADMIN);
    1256 0
        hub.updateSpokeConfig(assetId, spoke, spokeConfig);
    1257
    1258 0
        assertEq(hub.getSpokeConfig(assetId, spoke), spokeConfig);
    1259
      }
    1260
    1261 0
      function getUserInfo(
    1262
        ISpoke spoke,
    1263
        address user,
    1264
        uint256 reserveId
    1265 0
      ) internal view returns (ISpoke.UserPosition memory) {
    1266 0
        return spoke.getUserPosition(reserveId, user);
    1267
      }
    1268
    1269 0
      function getUserDebt(
    1270
        ISpoke spoke,
    1271
        address user,
    1272
        uint256 reserveId
    1273 0
      ) internal view returns (Debts memory data) {
    1274 0
        (data.drawnDebt, data.premiumDebt) = spoke.getUserDebt(reserveId, user);
    1275 0
        data.totalDebt = data.drawnDebt + data.premiumDebt;
    1276
      }
    1277
    1278 0
      function _isUsingAsCollateral(
    1279
        ISpoke spoke,
    1280
        uint256 reserveId,
    1281
        address user
    1282 0
      ) internal view returns (bool) {
    1283 0
        (bool res, ) = spoke.getUserReserveStatus(reserveId, user);
    1284
        return res;
    1285
      }
    1286
    1287 0
      function _isBorrowing(
    1288
        ISpoke spoke,
    1289
        uint256 reserveId,
    1290
        address user
    1291 0
      ) internal view returns (bool) {
    1292 0
        (, bool res) = spoke.getUserReserveStatus(reserveId, user);
    1293
        return res;
    1294
      }
    1295
    1296 0
      function getReserveInfo(
    1297
        ISpoke spoke,
    1298
        uint256 reserveId
    1299
      ) internal view returns (ISpoke.Reserve memory) {
    1300 0
        return spoke.getReserve(reserveId);
    1301
      }
    1302
    1303 0
      function _getReserveLastDynamicConfigKey(
    1304
        ISpoke spoke,
    1305
        uint256 reserveId
    1306 0
      ) internal view returns (uint24) {
    1307 0
        return spoke.getReserve(reserveId).dynamicConfigKey;
    1308
      }
    1309
    1310 0
      function _getLatestDynamicReserveConfig(
    1311
        ISpoke spoke,
    1312
        uint256 reserveId
    1313 0
      ) internal view returns (ISpoke.DynamicReserveConfig memory) {
    1314 0
        return
    1315 0
          spoke.getDynamicReserveConfig(reserveId, _getReserveLastDynamicConfigKey(spoke, reserveId));
    1316
      }
    1317
    1318
      function getSpokeInfo(
    1319
        uint256 assetId,
    1320
        address spoke
    1321
      ) internal view returns (IHub.SpokeData memory) {
    1322
        return hub1.getSpoke(assetId, spoke);
    1323
      }
    1324
    1325
      function getAssetInfo(uint256 assetId) internal view returns (IHub.Asset memory) {
    1326
        return hub1.getAsset(assetId);
    1327
      }
    1328
    1329 0
      function getAssetByReserveId(
    1330
        ISpoke spoke,
    1331
        uint256 reserveId
    1332 0
      ) internal view returns (uint256, IERC20) {
    1333 0
        ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
    1334 0
        (address underlying, ) = reserve.hub.getAssetUnderlyingAndDecimals(reserve.assetId);
    1335 0
        return (reserve.assetId, IERC20(underlying));
    1336
      }
    1337
    1338 0
      function getAssetUnderlyingByReserveId(
    1339
        ISpoke spoke,
    1340
        uint256 reserveId
    1341 0
      ) internal view returns (IERC20) {
    1342 0
        ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
    1343 0
        (address underlying, ) = reserve.hub.getAssetUnderlyingAndDecimals(reserve.assetId);
    1344
        return IERC20(underlying);
    1345
      }
    1346
    1347 0
      function getTotalWithdrawable(
    1348
        ISpoke spoke,
    1349
        uint256 reserveId,
    1350
        address user
    1351 0
      ) internal view returns (uint256) {
    1352 0
        return spoke.getUserSuppliedAssets(reserveId, user);
    1353
      }
    1354
    1355
      /// @dev Helper function to calculate asset amount corresponding to single added share
    1356 0
      function minimumAssetsPerAddedShare(IHub hub, uint256 assetId) internal view returns (uint256) {
    1357 0
        return hub.previewAddByShares(assetId, 1);
    1358
      }
    1359
    1360
      /// @dev Helper function to calculate asset amount corresponding to single drawn share
    1361 0
      function minimumAssetsPerDrawnShare(IHub hub, uint256 assetId) internal view returns (uint256) {
    1362 0
        return hub.previewRestoreByShares(assetId, 1);
    1363
      }
    1364
    1365
      /// @dev Helper function to calculate expected supplied assets based on amount to supply and current exchange rate
    1366
      /// taking potential donation into account
    1367 0
      function calculateEffectiveAddedAssets(
    1368
        uint256 assetsAmount,
    1369
        uint256 totalAddedAssets,
    1370
        uint256 totalAddedShares
    1371 0
      ) internal pure returns (uint256) {
    1372 0
        uint256 sharesAmount = assetsAmount.toSharesDown(totalAddedAssets, totalAddedShares);
    1373 0
        return
    1374 0
          sharesAmount.toAssetsDown(totalAddedAssets + assetsAmount, totalAddedShares + sharesAmount);
    1375
      }
    1376
    1377 0
      function getAddExRate(uint256 assetId) internal view returns (uint256) {
    1378 0
        return hub1.previewRemoveByShares(assetId, MAX_SUPPLY_AMOUNT);
    1379
      }
    1380
    1381 0
      function getDebtExRate(uint256 assetId) internal view returns (uint256) {
    1382 0
        return hub1.previewRestoreByShares(assetId, MAX_SUPPLY_AMOUNT);
    1383
      }
    1384
    1385
      function getAssetDrawnRate(IHub hub, uint256 assetId) internal view returns (uint256) {
    1386
        return hub.getAsset(assetId).drawnRate;
    1387
      }
    1388
    1389
      /// @dev Helper function to ensure supply exchange rate is monotonically increasing
    1390 0
      function _checkSupplyRateIncreasing(
    1391
        uint256 oldRate,
    1392
        uint256 newRate,
    1393
        string memory label
    1394
      ) internal pure {
    1395 0
        assertGe(newRate, oldRate, string.concat('supply rate monotonically increasing ', label));
    1396
      }
    1397
    1398 0
      function _checkDebtRateConstant(
    1399
        uint256 oldRate,
    1400
        uint256 newRate,
    1401
        string memory label
    1402
      ) internal pure {
    1403 0
        assertEq(newRate, oldRate, string.concat('debt rate should be constant ', label));
    1404
      }
    1405
    1406
      /// returns the USD value of the reserve normalized by it's decimals, in terms of WAD
    1407 0
      function _getValue(
    1408
        ISpoke spoke,
    1409
        uint256 reserveId,
    1410
        uint256 amount
    1411 0
      ) internal view returns (uint256) {
    1412 0
        return
    1413 0
          (amount * IPriceOracle(spoke.ORACLE()).getReservePrice(reserveId)).wadDivDown(
    1414 0
            10 ** _underlying(spoke, reserveId).decimals()
    1415
          );
    1416
      }
    1417
    1418
      /// returns the USD value of the reserve normalized by it's decimals, in terms of WAD
    1419 0
      function _getDebtValue(
    1420
        ISpoke spoke,
    1421
        uint256 reserveId,
    1422
        uint256 amount
    1423 0
      ) internal view returns (uint256) {
    1424 0
        return
    1425 0
          (amount * IPriceOracle(spoke.ORACLE()).getReservePrice(reserveId)).wadDivUp(
    1426 0
            10 ** _underlying(spoke, reserveId).decimals()
    1427
          );
    1428
      }
    1429
    1430
      /// @notice Convert 1 asset amount to equivalent amount in another asset.
    1431
      /// @notice Will contain precision loss due to conversion split into two steps.
    1432
      /// @return Converted amount of toAsset.
    1433 0
      function _convertAssetAmount(
    1434
        ISpoke spoke,
    1435
        uint256 reserveId,
    1436
        uint256 amount,
    1437
        uint256 toReserveId
    1438 0
      ) internal view returns (uint256) {
    1439 0
        return
    1440 0
          _convertValueToAmount(spoke, toReserveId, _convertAmountToValue(spoke, reserveId, amount));
    1441
      }
    1442
    1443
      /// @dev Helper function to calculate the amount of base and premium debt to restore
    1444
      // @return drawnRestored amount of drawn debt to restore
    1445
      // @return premiumRestored amount of premium debt to restore
    1446 0
      function _calculateExactRestoreAmount(
    1447
        uint256 drawn,
    1448
        uint256 premium,
    1449
        uint256 restoreAmount,
    1450
        uint256 assetId
    1451 0
      ) internal view returns (uint256, uint256) {
    1452 0
        if (restoreAmount <= premium) {
    1453 0
          return (0, restoreAmount);
    1454
        }
    1455 0
        uint256 drawnRestored = _min(drawn, restoreAmount - premium);
    1456
        // round drawn debt to nearest whole share
    1457 0
        drawnRestored = hub1.previewRestoreByShares(
    1458 0
          assetId,
    1459 0
          hub1.previewRestoreByAssets(assetId, drawnRestored)
    1460
        );
    1461 0
        return (drawnRestored, premium);
    1462
      }
    1463
    1464 0
      function _calculateExactRestoreAmount(
    1465
        ISpoke spoke,
    1466
        uint256 reserveId,
    1467
        address user,
    1468
        uint256 repayAmount
    1469 0
      ) internal view returns (uint256 baseRestored, uint256 premiumRestored) {
    1470 0
        (uint256 userDrawnDebt, uint256 userPremiumDebt) = spoke.getUserDebt(reserveId, user);
    1471 0
        return
    1472 0
          _calculateExactRestoreAmount(
    1473 0
            userDrawnDebt,
    1474 0
            userPremiumDebt,
    1475 0
            repayAmount,
    1476 0
            _spokeAssetId(spoke, reserveId)
    1477
          );
    1478
      }
    1479
    1480 0
      function _calculateRestoreAmounts(
    1481
        uint256 restoreAmount,
    1482
        uint256 drawn,
    1483
        uint256 premium
    1484 0
      ) internal pure returns (uint256 baseAmount, uint256 premiumAmount) {
    1485 0
        if (restoreAmount <= premium) {
    1486 0
          return (0, restoreAmount);
    1487
        }
    1488
    1489 0
        return (drawn.min(restoreAmount - premium), premium);
    1490
      }
    1491
    1492 0
      function _calculateRestoreAmounts(
    1493
        ISpoke spoke,
    1494
        uint256 reserveId,
    1495
        address user,
    1496
        uint256 repayAmount
    1497 0
      ) internal view returns (uint256 baseAmount, uint256 premiumAmount) {
    1498 0
        (uint256 userDrawnDebt, uint256 userPremiumDebt) = spoke.getUserDebt(reserveId, user);
    1499 0
        return _calculateRestoreAmounts(repayAmount, userDrawnDebt, userPremiumDebt);
    1500
      }
    1501
    1502 0
      function _getExpectedPremiumDelta(
    1503
        uint256 drawnIndex,
    1504
        uint256 oldPremiumShares,
    1505
        int256 oldPremiumOffsetRay,
    1506
        uint256 drawnShares,
    1507
        uint256 riskPremium,
    1508
        uint256 restoredPremiumRay
    1509 0
      ) internal pure returns (IHubBase.PremiumDelta memory) {
    1510 0
        uint256 premiumDebtRay = _calculatePremiumDebtRay(
    1511 0
          oldPremiumShares,
    1512 0
          oldPremiumOffsetRay,
    1513 0
          drawnIndex
    1514
        );
    1515
    1516 0
        uint256 newPremiumShares = drawnShares.percentMulUp(riskPremium);
    1517 0
        int256 newPremiumOffsetRay = _calculatePremiumAssetsRay(newPremiumShares, drawnIndex).signedSub(
    1518 0
          premiumDebtRay - restoredPremiumRay
    1519
        );
    1520
    1521 0
        return
    1522 0
          IHubBase.PremiumDelta({
    1523 0
            sharesDelta: newPremiumShares.toInt256() - oldPremiumShares.toInt256(),
    1524 0
            offsetRayDelta: newPremiumOffsetRay - oldPremiumOffsetRay,
    1525
            restoredPremiumRay: restoredPremiumRay
    1526
          });
    1527
      }
    1528
    1529 0
      function _getExpectedPremiumDelta(
    1530
        IHub hub,
    1531
        uint256 assetId,
    1532
        uint256 oldPremiumShares,
    1533
        int256 oldPremiumOffsetRay,
    1534
        uint256 drawnShares,
    1535
        uint256 riskPremium,
    1536
        uint256 restoredPremiumRay
    1537 0
      ) internal view returns (IHubBase.PremiumDelta memory) {
    1538 0
        return
    1539 0
          _getExpectedPremiumDelta({
    1540 0
            drawnIndex: hub.getAssetDrawnIndex(assetId),
    1541 0
            oldPremiumShares: oldPremiumShares,
    1542 0
            oldPremiumOffsetRay: oldPremiumOffsetRay,
    1543 0
            drawnShares: drawnShares,
    1544 0
            riskPremium: riskPremium,
    1545 0
            restoredPremiumRay: restoredPremiumRay
    1546
          });
    1547
      }
    1548
    1549 0
      function _getExpectedPremiumDelta(
    1550
        ISpoke spoke,
    1551
        address user,
    1552
        uint256 reserveId,
    1553
        uint256 repayAmount
    1554 0
      ) internal view virtual returns (IHubBase.PremiumDelta memory) {
    1555 0
        Debts memory userDebt = getUserDebt(spoke, user, reserveId);
    1556 0
        (, uint256 premiumAmountToRestore) = _calculateRestoreAmounts(
    1557 0
          repayAmount,
    1558 0
          userDebt.drawnDebt,
    1559 0
          userDebt.premiumDebt
    1560
        );
    1561
    1562 0
        ISpoke.UserPosition memory userPosition = spoke.getUserPosition(reserveId, user);
    1563 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    1564 0
        uint256 premiumDebtRay = _calculatePremiumDebtRay(
    1565 0
          hub1,
    1566 0
          assetId,
    1567 0
          userPosition.premiumShares,
    1568 0
          userPosition.premiumOffsetRay
    1569
        );
    1570
    1571 0
        uint256 restoredPremiumRay = (premiumAmountToRestore * WadRayMath.RAY).min(premiumDebtRay);
    1572
    1573 0
        return
    1574 0
          _getExpectedPremiumDelta({
    1575 0
            hub: hub1,
    1576 0
            assetId: assetId,
    1577 0
            oldPremiumShares: userPosition.premiumShares,
    1578 0
            oldPremiumOffsetRay: userPosition.premiumOffsetRay,
    1579 0
            drawnShares: 0, // risk premium is 0, so drawn shares do not matter here (otherwise they need to be updated with restored drawn shares amount)
    1580 0
            riskPremium: 0,
    1581 0
            restoredPremiumRay: restoredPremiumRay
    1582
          });
    1583
      }
    1584
    1585
      // in restore actions, premiumDelta is first reset to last user RP
    1586 0
      function _getExpectedPremiumDeltaForRestore(
    1587
        ISpoke spoke,
    1588
        address user,
    1589
        uint256 reserveId,
    1590
        uint256 repayAmount
    1591 0
      ) internal view virtual returns (IHubBase.PremiumDelta memory) {
    1592 0
        Debts memory userDebt = getUserDebt(spoke, user, reserveId);
    1593 0
        (uint256 drawnDebtToRestore, uint256 premiumAmountToRestore) = _calculateRestoreAmounts(
    1594 0
          repayAmount,
    1595 0
          userDebt.drawnDebt,
    1596 0
          userDebt.premiumDebt
    1597
        );
    1598
    1599
        {
    1600 0
          ISpoke.UserPosition memory userPosition = spoke.getUserPosition(reserveId, user);
    1601 0
          uint256 assetId = spoke.getReserve(reserveId).assetId;
    1602 0
          IHub hub = IHub(address(spoke.getReserve(reserveId).hub));
    1603 0
          uint256 premiumDebtRay = _calculatePremiumDebtRay(
    1604 0
            hub,
    1605 0
            assetId,
    1606 0
            userPosition.premiumShares,
    1607 0
            userPosition.premiumOffsetRay
    1608
          );
    1609
    1610 0
          uint256 restoredPremiumRay = (premiumAmountToRestore * WadRayMath.RAY).min(premiumDebtRay);
    1611 0
          uint256 restoredShares = drawnDebtToRestore.rayDivDown(hub.getAssetDrawnIndex(reserveId));
    1612 0
          uint256 riskPremium = _getUserLastRiskPremium(spoke, user);
    1613
    1614 0
          return
    1615 0
            _getExpectedPremiumDelta({
    1616 0
              hub: hub,
    1617 0
              assetId: assetId,
    1618 0
              oldPremiumShares: userPosition.premiumShares,
    1619 0
              oldPremiumOffsetRay: userPosition.premiumOffsetRay,
    1620 0
              drawnShares: userPosition.drawnShares - restoredShares,
    1621 0
              riskPremium: riskPremium,
    1622 0
              restoredPremiumRay: restoredPremiumRay
    1623
            });
    1624
        }
    1625
      }
    1626
    1627
      /// @dev Helper function to check consistent supplied amounts within accounting
    1628 0
      function _checkSuppliedAmounts(
    1629
        uint256 assetId,
    1630
        uint256 reserveId,
    1631
        ISpoke spoke,
    1632
        address user,
    1633
        uint256 expectedSuppliedAmount,
    1634
        string memory label
    1635
      ) internal view {
    1636 0
        uint256 expectedSuppliedShares = hub1.previewAddByAssets(assetId, expectedSuppliedAmount);
    1637 0
        assertEq(
    1638 0
          hub1.getAddedShares(assetId),
    1639 0
          expectedSuppliedShares,
    1640 0
          string(abi.encodePacked('asset supplied shares ', label))
    1641
        );
    1642 0
        assertEq(
    1643 0
          hub1.getAddedAssets(assetId) - _calculateBurntInterest(hub1, assetId),
    1644 0
          expectedSuppliedAmount,
    1645 0
          string(abi.encodePacked('asset supplied amount ', label))
    1646
        );
    1647 0
        assertEq(
    1648 0
          hub1.getSpokeAddedShares(assetId, address(spoke)),
    1649 0
          expectedSuppliedShares,
    1650 0
          string(abi.encodePacked('spoke supplied shares ', label))
    1651
        );
    1652 0
        assertEq(
    1653 0
          hub1.getSpokeAddedAssets(assetId, address(spoke)),
    1654 0
          expectedSuppliedAmount,
    1655 0
          string(abi.encodePacked('spoke supplied amount ', label))
    1656
        );
    1657 0
        assertEq(
    1658 0
          spoke.getReserveSuppliedShares(reserveId),
    1659 0
          expectedSuppliedShares,
    1660 0
          string(abi.encodePacked('reserve supplied shares ', label))
    1661
        );
    1662 0
        assertEq(
    1663 0
          spoke.getReserveSuppliedAssets(reserveId),
    1664 0
          expectedSuppliedAmount,
    1665 0
          string(abi.encodePacked('reserve supplied amount ', label))
    1666
        );
    1667 0
        assertEq(
    1668 0
          spoke.getUserSuppliedShares(reserveId, user),
    1669 0
          expectedSuppliedShares,
    1670 0
          string(abi.encodePacked('user supplied shares ', label))
    1671
        );
    1672 0
        assertEq(
    1673 0
          spoke.getUserSuppliedAssets(reserveId, user),
    1674 0
          expectedSuppliedAmount,
    1675 0
          string(abi.encodePacked('user supplied amount ', label))
    1676
        );
    1677
      }
    1678
    1679 0
      function _assertUserDebt(
    1680
        ISpoke spoke,
    1681
        uint256 reserveId,
    1682
        address user,
    1683
        uint256 expectedDrawnDebt,
    1684
        uint256 expectedPremiumDebt,
    1685
        string memory label
    1686
      ) internal view {
    1687 0
        (uint256 actualDrawnDebt, uint256 actualPremiumDebt) = spoke.getUserDebt(reserveId, user);
    1688 0
        assertApproxEqAbs(
    1689 0
          actualDrawnDebt,
    1690 0
          expectedDrawnDebt,
    1691 0
          1,
    1692 0
          string.concat('user drawn debt ', label)
    1693
        );
    1694 0
        assertApproxEqAbs(
    1695 0
          actualPremiumDebt,
    1696 0
          expectedPremiumDebt,
    1697 0
          3,
    1698 0
          string.concat('user premium debt ', label)
    1699
        );
    1700 0
        assertApproxEqAbs(
    1701 0
          spoke.getUserTotalDebt(reserveId, user),
    1702 0
          expectedDrawnDebt + expectedPremiumDebt,
    1703 0
          3,
    1704 0
          string.concat('user total debt ', label)
    1705
        );
    1706
      }
    1707
    1708 0
      function _assertReserveDebt(
    1709
        ISpoke spoke,
    1710
        uint256 reserveId,
    1711
        uint256 expectedDrawnDebt,
    1712
        uint256 expectedPremiumDebt,
    1713
        string memory label
    1714
      ) internal view {
    1715 0
        (uint256 actualDrawnDebt, uint256 actualPremiumDebt) = spoke.getReserveDebt(reserveId);
    1716 0
        assertApproxEqAbs(
    1717 0
          actualDrawnDebt,
    1718 0
          expectedDrawnDebt,
    1719 0
          1,
    1720 0
          string.concat('reserve drawn debt ', label)
    1721
        );
    1722 0
        assertApproxEqAbs(
    1723 0
          actualPremiumDebt,
    1724 0
          expectedPremiumDebt,
    1725 0
          3,
    1726 0
          string.concat('reserve premium debt ', label)
    1727
        );
    1728 0
        assertApproxEqAbs(
    1729 0
          spoke.getReserveTotalDebt(reserveId),
    1730 0
          expectedDrawnDebt + expectedPremiumDebt,
    1731 0
          3,
    1732 0
          string.concat('reserve total debt ', label)
    1733
        );
    1734
      }
    1735
    1736 0
      function _assertSpokeDebt(
    1737
        ISpoke spoke,
    1738
        uint256 reserveId,
    1739
        uint256 expectedDrawnDebt,
    1740
        uint256 expectedPremiumDebt,
    1741
        string memory label
    1742 0
      ) internal view {
    1743 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    1744 0
        (uint256 actualDrawnDebt, uint256 actualPremiumDebt) = hub1.getSpokeOwed(
    1745
          assetId,
    1746 0
          address(spoke)
    1747
        );
    1748 0
        assertApproxEqAbs(
    1749 0
          actualDrawnDebt,
    1750 0
          expectedDrawnDebt,
    1751 0
          1,
    1752 0
          string.concat('spoke drawn debt ', label)
    1753
        );
    1754 0
        assertApproxEqAbs(
    1755 0
          actualPremiumDebt,
    1756 0
          expectedPremiumDebt,
    1757 0
          3,
    1758 0
          string.concat('spoke premium debt ', label)
    1759
        );
    1760 0
        assertApproxEqAbs(
    1761 0
          hub1.getSpokeTotalOwed(assetId, address(spoke)),
    1762 0
          expectedDrawnDebt + expectedPremiumDebt,
    1763 0
          3,
    1764 0
          string.concat('spoke total debt ', label)
    1765
        );
    1766
      }
    1767
    1768 0
      function _assertAssetDebt(
    1769
        ISpoke spoke,
    1770
        uint256 reserveId,
    1771
        uint256 expectedDrawnDebt,
    1772
        uint256 expectedPremiumDebt,
    1773
        string memory label
    1774
      ) internal view {
    1775 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    1776 0
        (uint256 actualDrawnDebt, uint256 actualPremiumDebt) = hub1.getAssetOwed(assetId);
    1777 0
        assertApproxEqAbs(
    1778 0
          actualDrawnDebt,
    1779 0
          expectedDrawnDebt,
    1780 0
          1,
    1781 0
          string.concat('asset drawn debt ', label)
    1782
        );
    1783 0
        assertApproxEqAbs(
    1784 0
          actualPremiumDebt,
    1785 0
          expectedPremiumDebt,
    1786 0
          3,
    1787 0
          string.concat('asset premium debt ', label)
    1788
        );
    1789 0
        assertApproxEqAbs(
    1790 0
          hub1.getAssetTotalOwed(assetId),
    1791 0
          expectedDrawnDebt + expectedPremiumDebt,
    1792 0
          3,
    1793 0
          string.concat('asset total debt ', label)
    1794
        );
    1795
      }
    1796
    1797 0
      function _assertSingleUserProtocolDebt(
    1798
        ISpoke spoke,
    1799
        uint256 reserveId,
    1800
        address user,
    1801
        uint256 expectedDrawnDebt,
    1802
        uint256 expectedPremiumDebt,
    1803
        string memory label
    1804
      ) internal view {
    1805 0
        _assertUserDebt(spoke, reserveId, user, expectedDrawnDebt, expectedPremiumDebt, label);
    1806
    1807 0
        _assertReserveDebt(spoke, reserveId, expectedDrawnDebt, expectedPremiumDebt, label);
    1808
    1809 0
        _assertSpokeDebt(spoke, reserveId, expectedDrawnDebt, expectedPremiumDebt, label);
    1810
    1811 0
        _assertAssetDebt(spoke, reserveId, expectedDrawnDebt, expectedPremiumDebt, label);
    1812
      }
    1813
    1814 0
      function _assertUserSupply(
    1815
        ISpoke spoke,
    1816
        uint256 reserveId,
    1817
        address user,
    1818
        uint256 expectedSuppliedAmount,
    1819
        string memory label
    1820
      ) internal view {
    1821 0
        assertApproxEqAbs(
    1822 0
          spoke.getUserSuppliedAssets(reserveId, user),
    1823 0
          expectedSuppliedAmount,
    1824 0
          3,
    1825 0
          string.concat('user supplied amount ', label)
    1826
        );
    1827
      }
    1828
    1829 0
      function _assertReserveSupply(
    1830
        ISpoke spoke,
    1831
        uint256 reserveId,
    1832
        uint256 expectedSuppliedAmount,
    1833
        string memory label
    1834
      ) internal view {
    1835 0
        assertApproxEqAbs(
    1836 0
          spoke.getReserveSuppliedAssets(reserveId),
    1837 0
          expectedSuppliedAmount,
    1838 0
          3,
    1839 0
          string.concat('reserve supplied amount ', label)
    1840
        );
    1841
      }
    1842
    1843 0
      function _assertSpokeSupply(
    1844
        ISpoke spoke,
    1845
        uint256 reserveId,
    1846
        uint256 expectedSuppliedAmount,
    1847
        string memory label
    1848
      ) internal view {
    1849 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    1850 0
        assertApproxEqAbs(
    1851 0
          hub1.getSpokeAddedAssets(assetId, address(spoke)),
    1852 0
          expectedSuppliedAmount,
    1853 0
          3,
    1854 0
          string.concat('spoke supplied amount ', label)
    1855
        );
    1856
      }
    1857
    1858 0
      function _assertAssetSupply(
    1859
        ISpoke spoke,
    1860
        uint256 reserveId,
    1861
        uint256 expectedSuppliedAmount,
    1862
        string memory label
    1863
      ) internal view {
    1864 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    1865 0
        assertApproxEqAbs(
    1866 0
          hub1.getAddedAssets(assetId) - _calculateBurntInterest(hub1, assetId),
    1867 0
          expectedSuppliedAmount,
    1868 0
          3,
    1869 0
          string.concat('asset supplied amount ', label)
    1870
        );
    1871
      }
    1872
    1873 0
      function _assertSingleUserProtocolSupply(
    1874
        ISpoke spoke,
    1875
        uint256 reserveId,
    1876
        address user,
    1877
        uint256 expectedSuppliedAmount,
    1878
        string memory label
    1879
      ) internal view {
    1880 0
        _assertUserSupply(spoke, reserveId, user, expectedSuppliedAmount, label);
    1881
    1882 0
        _assertReserveSupply(spoke, reserveId, expectedSuppliedAmount, label);
    1883
    1884 0
        _assertSpokeSupply(spoke, reserveId, expectedSuppliedAmount, label);
    1885
    1886 0
        _assertAssetSupply(spoke, reserveId, expectedSuppliedAmount, label);
    1887
      }
    1888
    1889 0
      function _convertAmountToValue(
    1890
        ISpoke spoke,
    1891
        uint256 reserveId,
    1892
        uint256 amount
    1893 0
      ) internal view returns (uint256) {
    1894 0
        return
    1895 0
          _convertAmountToValue(
    1896 0
            amount,
    1897 0
            IPriceOracle(spoke.ORACLE()).getReservePrice(reserveId),
    1898 0
            10 ** _underlying(spoke, reserveId).decimals()
    1899
          );
    1900
      }
    1901
    1902 0
      function _convertAmountToValue(
    1903
        uint256 amount,
    1904
        uint256 assetPrice,
    1905
        uint256 assetUnit
    1906 0
      ) internal pure returns (uint256) {
    1907 0
        return (amount * assetPrice).wadDivUp(assetUnit);
    1908
      }
    1909
    1910 0
      function _convertValueToAmount(
    1911
        ISpoke spoke,
    1912
        uint256 reserveId,
    1913
        uint256 valueAmount
    1914 0
      ) internal view returns (uint256) {
    1915 0
        return
    1916 0
          _convertValueToAmount(
    1917 0
            valueAmount,
    1918 0
            IPriceOracle(spoke.ORACLE()).getReservePrice(reserveId),
    1919 0
            10 ** _underlying(spoke, reserveId).decimals()
    1920
          );
    1921
      }
    1922
    1923 0
      function _convertValueToAmount(
    1924
        uint256 valueAmount,
    1925
        uint256 assetPrice,
    1926
        uint256 assetUnit
    1927 0
      ) internal pure returns (uint256) {
    1928 0
        return ((valueAmount * assetUnit) / assetPrice).fromWadDown();
    1929
      }
    1930
    1931
      /**
    1932
       * @notice Returns the required debt amount to ensure user position is ~ a certain health factor.
    1933
       * @param desiredHf The desired health factor to be at.
    1934
       */
    1935 0
      function _getRequiredDebtAmountForHf(
    1936
        ISpoke spoke,
    1937
        address user,
    1938
        uint256 reserveId,
    1939
        uint256 desiredHf
    1940 0
      ) internal view returns (uint256 requiredDebtAmount) {
    1941 0
        uint256 requiredDebtAmountValue = _getRequiredDebtValueForHf(spoke, user, desiredHf);
    1942 0
        return _convertValueToAmount(spoke, reserveId, requiredDebtAmountValue);
    1943
      }
    1944
    1945
      /**
    1946
       * @notice Returns the required debt in value terms to ensure user position is below a certain health factor.
    1947
       */
    1948 0
      function _getRequiredDebtValueForHf(
    1949
        ISpoke spoke,
    1950
        address user,
    1951
        uint256 desiredHf
    1952 0
      ) internal view returns (uint256 requiredDebtValue) {
    1953 0
        ISpoke.UserAccountData memory userAccountData = spoke.getUserAccountData(user);
    1954
    1955 0
        requiredDebtValue =
    1956 0
          userAccountData.totalCollateralValue.wadMulUp(userAccountData.avgCollateralFactor).wadDivUp(
    1957 0
            desiredHf
    1958
          ) -
    1959 0
          userAccountData.totalDebtValue;
    1960
      }
    1961
    1962 0
      function _getUserHealthFactor(ISpoke spoke, address user) internal view returns (uint256) {
    1963 0
        return spoke.getUserAccountData(user).healthFactor;
    1964
      }
    1965
    1966 0
      function _getUserLastRiskPremium(ISpoke spoke, address user) internal view returns (uint256) {
    1967 0
        return spoke.getUserLastRiskPremium(user);
    1968
      }
    1969
    1970 0
      function _getUserRiskPremium(ISpoke spoke, address user) internal view returns (uint256) {
    1971 0
        return spoke.getUserAccountData(user).riskPremium;
    1972
      }
    1973
    1974 0
      function _approxRelFromBps(uint256 bps) internal pure returns (uint256) {
    1975 0
        return (bps * 1e18) / 100_00;
    1976
      }
    1977
    1978 0
      function _min(uint256 a, uint256 b) internal pure returns (uint256) {
    1979 0
        return a < b ? a : b;
    1980
      }
    1981
    1982 0
      function _max(uint256 a, uint256 b) internal pure returns (uint256) {
    1983 0
        return a > b ? a : b;
    1984
      }
    1985
    1986 0
      function _getTargetHealthFactor(ISpoke spoke) internal view returns (uint128) {
    1987 0
        return spoke.getLiquidationConfig().targetHealthFactor;
    1988
      }
    1989
    1990 0
      function _calcMinimumCollAmount(
    1991
        ISpoke spoke,
    1992
        uint256 collReserveId,
    1993
        uint256 debtReserveId,
    1994
        uint256 debtAmount
    1995 0
      ) internal view returns (uint256) {
    1996 0
        if (debtAmount == 0) return 1;
    1997 0
        IPriceOracle oracle = IPriceOracle(spoke.ORACLE());
    1998 0
        ISpoke.Reserve memory collData = spoke.getReserve(collReserveId);
    1999 0
        ISpoke.DynamicReserveConfig memory collDynConf = _getLatestDynamicReserveConfig(
    2000 0
          spoke,
    2001 0
          collReserveId
    2002
        );
    2003
    2004 0
        uint256 collPrice = oracle.getReservePrice(collReserveId);
    2005 0
        uint256 collAssetUnits = 10 ** hub1.getAsset(collData.assetId).decimals;
    2006
    2007 0
        ISpoke.Reserve memory debtData = spoke.getReserve(debtReserveId);
    2008 0
        uint256 debtAssetUnits = 10 ** hub1.getAsset(debtData.assetId).decimals;
    2009 0
        uint256 debtPrice = oracle.getReservePrice(debtReserveId);
    2010
    2011 0
        uint256 normalizedDebtAmount = (debtAmount * debtPrice).wadDivDown(debtAssetUnits);
    2012 0
        uint256 normalizedCollPrice = collPrice.wadDivDown(collAssetUnits);
    2013
    2014 0
        return
    2015 0
          normalizedDebtAmount.wadDivUp(
    2016 0
            normalizedCollPrice.toWad().percentMulDown(collDynConf.collateralFactor)
    2017
          );
    2018
      }
    2019
    2020
      /// @dev Calculate expected debt index based on input params
    2021 0
      function _calculateExpectedDrawnIndex(
    2022
        uint256 initialDrawnIndex,
    2023
        uint96 borrowRate,
    2024
        uint40 startTime
    2025 0
      ) internal view returns (uint256) {
    2026 0
        return initialDrawnIndex.rayMulUp(MathUtils.calculateLinearInterest(borrowRate, startTime));
    2027
      }
    2028
    2029
      /// @dev Calculate expected debt index and drawn debt based on input params
    2030 0
      function calculateExpectedDebt(
    2031
        uint256 initialDrawnShares,
    2032
        uint256 initialDrawnIndex,
    2033
        uint96 borrowRate,
    2034
        uint40 startTime
    2035 0
      ) internal view returns (uint256 newDrawnIndex, uint256 newDrawnDebt) {
    2036 0
        newDrawnIndex = _calculateExpectedDrawnIndex(initialDrawnIndex, borrowRate, startTime);
    2037 0
        newDrawnDebt = initialDrawnShares.rayMulUp(newDrawnIndex);
    2038
      }
    2039
    2040
      /// @dev Calculate expected drawn debt based on specified borrow rate
    2041 0
      function _calculateExpectedDrawnDebt(
    2042
        uint256 initialDebt,
    2043
        uint96 borrowRate,
    2044
        uint40 startTime
    2045 0
      ) internal view returns (uint256) {
    2046 0
        return MathUtils.calculateLinearInterest(borrowRate, startTime).rayMulUp(initialDebt);
    2047
      }
    2048
    2049
      /// @dev Calculate expected premium debt based on change in drawn debt and user rp
    2050 0
      function _calculateExpectedPremiumDebt(
    2051
        uint256 initialDrawnDebt,
    2052
        uint256 currentDrawnDebt,
    2053
        uint256 userRiskPremium
    2054 0
      ) internal pure returns (uint256) {
    2055 0
        return (currentDrawnDebt - initialDrawnDebt).percentMulUp(userRiskPremium);
    2056
      }
    2057
    2058
      /// @dev Helper function to get asset drawn debt
    2059 0
      function getAssetDrawnDebt(uint256 assetId) internal view returns (uint256) {
    2060 0
        (uint256 drawn, ) = hub1.getAssetOwed(assetId);
    2061
        return drawn;
    2062
      }
    2063
    2064
      /// @dev Helper function to calculate burnt interest in assets terms (originated from virtual shares and assets)
    2065 0
      function _calculateBurntInterest(IHub hub, uint256 assetId) internal view returns (uint256) {
    2066
        return
    2067 0
          hub.getAddedAssets(assetId) - hub.previewRemoveByShares(assetId, hub.getAddedShares(assetId));
    2068
      }
    2069
    2070 0
      function _calculatePremiumDebt(
    2071
        IHub hub,
    2072
        uint256 assetId,
    2073
        uint256 premiumShares,
    2074
        int256 premiumOffsetRay
    2075 0
      ) internal view returns (uint256) {
    2076 0
        return _calculatePremiumDebtRay(hub, assetId, premiumShares, premiumOffsetRay).fromRayUp();
    2077
      }
    2078
    2079 0
      function _calculatePremiumDebtRay(
    2080
        IHub hub,
    2081
        uint256 assetId,
    2082
        uint256 premiumShares,
    2083
        int256 premiumOffsetRay
    2084 0
      ) internal view returns (uint256) {
    2085 0
        uint256 drawnIndex = hub.getAssetDrawnIndex(assetId);
    2086 0
        return _calculatePremiumDebtRay(premiumShares, premiumOffsetRay, drawnIndex);
    2087
      }
    2088
    2089 0
      function _calculatePremiumDebtRay(
    2090
        uint256 premiumShares,
    2091
        int256 premiumOffsetRay,
    2092
        uint256 drawnIndex
    2093 0
      ) internal pure returns (uint256) {
    2094 0
        return ((premiumShares * drawnIndex).toInt256() - premiumOffsetRay).toUint256();
    2095
      }
    2096
    2097 0
      function _calculatePremiumDebtRay(
    2098
        ISpoke spoke,
    2099
        uint256 reserveId,
    2100
        uint256 premiumShares,
    2101
        int256 premiumOffsetRay
    2102 0
      ) internal view returns (uint256) {
    2103 0
        IHub hub = _hub(spoke, reserveId);
    2104 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    2105 0
        return _calculatePremiumDebtRay(hub, assetId, premiumShares, premiumOffsetRay);
    2106
      }
    2107
    2108 0
      function _calculatePremiumDebtRay(
    2109
        ISpoke spoke,
    2110
        uint256 reserveId,
    2111
        address user
    2112 0
      ) internal view returns (uint256) {
    2113 0
        ISpoke.UserPosition memory userPosition = spoke.getUserPosition(reserveId, user);
    2114 0
        return
    2115 0
          _calculatePremiumDebtRay(
    2116 0
            spoke,
    2117 0
            reserveId,
    2118 0
            userPosition.premiumShares,
    2119 0
            userPosition.premiumOffsetRay
    2120
          );
    2121
      }
    2122
    2123 0
      function _calculatePremiumAssetsRay(
    2124
        uint256 premiumShares,
    2125
        uint256 drawnIndex
    2126 0
      ) internal pure returns (uint256) {
    2127 0
        return premiumShares * drawnIndex;
    2128
      }
    2129
    2130 0
      function _calculatePremiumAssetsRay(
    2131
        IHub hub,
    2132
        uint256 assetId,
    2133
        uint256 premiumShares
    2134 0
      ) internal view returns (uint256) {
    2135 0
        return _calculatePremiumAssetsRay(premiumShares, hub.getAssetDrawnIndex(assetId));
    2136
      }
    2137
    2138
      /// @dev Helper function to withdraw fees from the treasury spoke
    2139 0
      function _withdrawLiquidityFees(IHub hub, uint256 assetId, uint256 amount) internal {
    2140 0
        Utils.mintFeeShares(hub, assetId, ADMIN);
    2141 0
        uint256 fees = hub.getSpokeAddedAssets(assetId, address(treasurySpoke));
    2142
    2143 0
        if (amount > fees) {
    2144 0
          amount = fees;
    2145
        }
    2146 0
        if (amount == 0) {
    2147 0
          return; // nothing to withdraw
    2148
        }
    2149 0
        vm.prank(TREASURY_ADMIN);
    2150 0
        treasurySpoke.withdraw(assetId, amount, address(treasurySpoke));
    2151
      }
    2152
    2153 0
      function _assumeValidSupplier(address user) internal view {
    2154 0
        vm.assume(
    2155 0
          user != address(0) &&
    2156 0
            user != address(hub1) &&
    2157 0
            user != address(spoke1) &&
    2158 0
            user != address(spoke2) &&
    2159 0
            user != address(spoke3) &&
    2160 0
            user != _getProxyAdminAddress(address(spoke1)) &&
    2161 0
            user != _getProxyAdminAddress(address(spoke2)) &&
    2162 0
            user != _getProxyAdminAddress(address(spoke3))
    2163
        );
    2164
      }
    2165
    2166 0
      function _getAssetLiquidityFee(uint256 assetId) internal view returns (uint256) {
    2167 0
        return hub1.getAssetConfig(assetId).liquidityFee;
    2168
      }
    2169
    2170 0
      function _getFeeReceiver(IHub hub, uint256 assetId) internal view returns (address) {
    2171 0
        return hub.getAssetConfig(assetId).feeReceiver;
    2172
      }
    2173
    2174 0
      function _getFeeReceiver(ISpoke spoke, uint256 reserveId) internal view returns (address) {
    2175 0
        return _getFeeReceiver(_hub(spoke, reserveId), spoke.getReserve(reserveId).assetId);
    2176
      }
    2177
    2178 0
      function _getCollateralRisk(ISpoke spoke, uint256 reserveId) internal view returns (uint24) {
    2179 0
        return spoke.getReserveConfig(reserveId).collateralRisk;
    2180
      }
    2181
    2182 0
      function _getCollateralFactor(ISpoke spoke, uint256 reserveId) internal view returns (uint16) {
    2183 0
        return _getLatestDynamicReserveConfig(spoke, reserveId).collateralFactor;
    2184
      }
    2185
    2186 0
      function _getCollateralFactor(
    2187
        ISpoke spoke,
    2188
        uint256 reserveId,
    2189
        address user
    2190 0
      ) internal view returns (uint16) {
    2191 0
        uint24 dynamicConfigKey = spoke.getUserPosition(reserveId, user).dynamicConfigKey;
    2192 0
        return spoke.getDynamicReserveConfig(reserveId, dynamicConfigKey).collateralFactor;
    2193
      }
    2194
    2195
      function _getCollateralFactor(
    2196
        ISpoke spoke,
    2197
        function(ISpoke) internal view returns (uint256) reserveId
    2198
      ) internal view returns (uint16) {
    2199
        return _getLatestDynamicReserveConfig(spoke, reserveId(spoke)).collateralFactor;
    2200
      }
    2201
    2202 0
      function _hasRole(
    2203
        IAccessManager authority,
    2204
        uint64 role,
    2205
        address account
    2206 0
      ) internal view returns (bool) {
    2207 0
        (bool hasRole, ) = authority.hasRole(role, account);
    2208
        return hasRole;
    2209
      }
    2210
    2211 0
      function _randomBps() internal returns (uint16) {
    2212 0
        return vm.randomUint(0, PercentageMath.PERCENTAGE_FACTOR).toUint16();
    2213
      }
    2214
    2215 0
      function _hub(ISpoke spoke, uint256 reserveId) internal view returns (IHub) {
    2216 0
        return IHub(address(spoke.getReserve(reserveId).hub));
    2217
      }
    2218
    2219 0
      function _spokeAssetId(ISpoke spoke, uint256 reserveId) internal view returns (uint256) {
    2220 0
        return spoke.getReserve(reserveId).assetId;
    2221
      }
    2222
    2223 0
      function _underlying(ISpoke spoke, uint256 reserveId) internal view returns (TestnetERC20) {
    2224 0
        return TestnetERC20(spoke.getReserve(reserveId).underlying);
    2225
      }
    2226
    2227 0
      function _approveAllUnderlying(ISpoke spoke, address owner, address spender) internal {
    2228 0
        for (uint256 reserveId; reserveId < spoke.getReserveCount(); ++reserveId) {
    2229 0
          TestnetERC20 underlying = _underlying(spoke, reserveId);
    2230 0
          vm.prank(owner);
    2231 0
          underlying.approve(spender, UINT256_MAX);
    2232
        }
    2233
      }
    2234
    2235
      function _deploySpokeWithOracle(
    2236
        address /*proxyAdminOwner*/,
    2237
        address _accessManager,
    2238
        string memory _oracleDesc
    2239
      ) internal returns (ISpoke, IAaveOracle) {
    2240
        // address deployer = makeAddr('deployer');
    2241
        // address predictedSpoke = vm.computeCreateAddress(deployer, vm.getNonce(deployer));
    2242 37×
        IAaveOracle oracle = new AaveOracle(address(1), 8, _oracleDesc);
    2243 26×
        SpokeInstance spokeImpl = new SpokeInstance(address(oracle));
    2244 39×
        spokeImpl.initialize(_accessManager);
    2245
        ISpoke spoke = ISpoke(
    2246
          spokeImpl
    2247
          // _proxify(
    2248
          //   deployer,
    2249
          //   spokeImpl,
    2250
          //   proxyAdminOwner,
    2251
          //   abi.encodeCall(Spoke.initialize, (_accessManager))
    2252
          // )
    2253
        );
    2254 42×
        AaveOracle(address(oracle)).setSpoke(address(spoke));
    2255
        // assertEq(address(spoke), predictedSpoke, 'predictedSpoke');
    2256 63×
        _assertEq(spoke.ORACLE(), address(oracle));
    2257 63×
        _assertEq(oracle.SPOKE(), address(spoke));
    2258
        return (spoke, oracle);
    2259
      }
    2260
    2261
      function _getDefaultReserveConfig(
    2262
        uint24 collateralRisk
    2263 0
      ) internal pure returns (ISpoke.ReserveConfig memory) {
    2264
        return
    2265 42×
          ISpoke.ReserveConfig({
    2266
            paused: false,
    2267
            frozen: false,
    2268
            borrowable: true,
    2269
            liquidatable: true,
    2270
            receiveSharesEnabled: true,
    2271
            collateralRisk: collateralRisk
    2272
          });
    2273
      }
    2274
    2275
      function _proxify(
    2276
        address deployer,
    2277
        address impl,
    2278
        address proxyAdminOwner,
    2279
        bytes memory initData
    2280
      ) internal returns (address) {
    2281
        vm.prank(deployer);
    2282
        TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
    2283
          impl,
    2284
          proxyAdminOwner,
    2285
          initData
    2286
        );
    2287
        return address(proxy);
    2288
      }
    2289
    2290 0
      function assertEq(IHubBase.PremiumDelta memory a, IHubBase.PremiumDelta memory b) internal pure {
    2291 0
        assertEq(a.sharesDelta, b.sharesDelta, 'sharesDelta');
    2292 0
        assertEq(a.offsetRayDelta, b.offsetRayDelta, 'offsetRayDelta');
    2293 0
        assertEq(a.restoredPremiumRay, b.restoredPremiumRay, 'restoredPremiumRay');
    2294 0
        assertEq(abi.encode(a), abi.encode(b));
    2295
      }
    2296
    2297 0
      function assertEq(IHub.AssetConfig memory a, IHub.AssetConfig memory b) internal pure {
    2298 0
        assertEq(a.feeReceiver, b.feeReceiver, 'feeReceiver');
    2299 0
        assertEq(a.liquidityFee, b.liquidityFee, 'liquidityFee');
    2300 0
        assertEq(a.irStrategy, b.irStrategy, 'irStrategy');
    2301 0
        assertEq(a.reinvestmentController, b.reinvestmentController, 'reinvestmentController');
    2302 0
        assertEq(abi.encode(a), abi.encode(b));
    2303
      }
    2304
    2305 0
      function assertEq(IHub.SpokeConfig memory a, IHub.SpokeConfig memory b) internal pure {
    2306 0
        assertEq(a.addCap, b.addCap, 'addCap');
    2307 0
        assertEq(a.drawCap, b.drawCap, 'drawCap');
    2308 0
        assertEq(a.riskPremiumThreshold, b.riskPremiumThreshold, 'riskPremiumThreshold');
    2309 0
        assertEq(a.active, b.active, 'active');
    2310 0
        assertEq(a.paused, b.paused, 'paused');
    2311 0
        assertEq(abi.encode(a), abi.encode(b));
    2312
      }
    2313
    2314 0
      function assertEq(
    2315
        ISpoke.LiquidationConfig memory a,
    2316
        ISpoke.LiquidationConfig memory b
    2317
      ) internal pure {
    2318 0
        assertEq(a.targetHealthFactor, b.targetHealthFactor, 'targetHealthFactor');
    2319 0
        assertEq(a.liquidationBonusFactor, b.liquidationBonusFactor, 'liquidationBonusFactor');
    2320 0
        assertEq(a.healthFactorForMaxBonus, b.healthFactorForMaxBonus, 'healthFactorForMaxBonus');
    2321 0
        assertEq(abi.encode(a), abi.encode(b));
    2322
      }
    2323
    2324 0
      function assertEq(ISpoke.ReserveConfig memory a, ISpoke.ReserveConfig memory b) internal pure {
    2325 0
        assertEq(a.paused, b.paused, 'paused');
    2326 0
        assertEq(a.frozen, b.frozen, 'frozen');
    2327 0
        assertEq(a.borrowable, b.borrowable, 'borrowable');
    2328 0
        assertEq(a.receiveSharesEnabled, b.receiveSharesEnabled, 'receiveSharesEnabled');
    2329 0
        assertEq(a.collateralRisk, b.collateralRisk, 'collateralRisk');
    2330 0
        assertEq(abi.encode(a), abi.encode(b));
    2331
      }
    2332
    2333 0
      function assertEq(
    2334
        ISpoke.DynamicReserveConfig memory a,
    2335
        ISpoke.DynamicReserveConfig memory b
    2336
      ) internal pure {
    2337 0
        assertEq(a.collateralFactor, b.collateralFactor, 'collateralFactor');
    2338 0
        assertEq(a.maxLiquidationBonus, b.maxLiquidationBonus, 'maxLiquidationBonus');
    2339 0
        assertEq(a.liquidationFee, b.liquidationFee, 'liquidationFee');
    2340 0
        assertEq(abi.encode(a), abi.encode(b));
    2341
      }
    2342
    2343 0
      function assertEq(
    2344
        IAssetInterestRateStrategy.InterestRateData memory a,
    2345
        IAssetInterestRateStrategy.InterestRateData memory b
    2346
      ) internal pure {
    2347 0
        assertEq(a.optimalUsageRatio, b.optimalUsageRatio, 'optimalUsageRatio');
    2348 0
        assertEq(a.baseVariableBorrowRate, b.baseVariableBorrowRate, 'baseVariableBorrowRate');
    2349 0
        assertEq(a.variableRateSlope1, b.variableRateSlope1, 'variableRateSlope1');
    2350 0
        assertEq(a.variableRateSlope2, b.variableRateSlope2, 'variableRateSlope2');
    2351 0
        assertEq(abi.encode(a), abi.encode(b));
    2352
      }
    2353
    2354
      function _assertEq(address a, address b, string memory errorMessage) internal pure {
    2355
        if (a != b) {
    2356 0
          console.log('a', a);
    2357 0
          console.log('b', b);
    2358
          console.log('errorMessage', errorMessage);
    2359
        }
    2360 11×
        require(a == b, errorMessage);
    2361
      }
    2362
    2363
      function _assertEq(address a, address b) internal pure {
    2364 25×
        _assertEq(a, b, "address mismatch");
    2365
      }
    2366
    2367
      function _calculateExpectedFees(
    2368
        uint256 drawnIncrease,
    2369
        uint256 premiumIncrease,
    2370
        uint256 liquidityFee
    2371
      ) internal pure returns (uint256) {
    2372
        return (drawnIncrease + premiumIncrease).percentMulDown(liquidityFee);
    2373
      }
    2374
    2375 0
      function _calculateExpectedFeesAmount(
    2376
        uint256 initialDrawnShares,
    2377
        uint256 initialPremiumShares,
    2378
        uint256 liquidityFee,
    2379
        uint256 indexDelta
    2380 0
      ) internal pure returns (uint256 feesAmount) {
    2381
        return
    2382 0
          indexDelta.rayMulUp(initialDrawnShares + initialPremiumShares).percentMulDown(liquidityFee);
    2383
      }
    2384
    2385
      /// @dev Get the liquidation bonus for a given reserve at a user HF
    2386
      function _getLiquidationBonus(
    2387
        ISpoke spoke,
    2388
        uint256 reserveId,
    2389
        address user,
    2390
        uint256 healthFactor
    2391
      ) internal view returns (uint256) {
    2392
        return spoke.getLiquidationBonus(reserveId, user, healthFactor);
    2393
      }
    2394
    2395
      /**
    2396
       * @notice Returns the required debt amount in value terms to ensure user position is above a certain health factor.
    2397
       * @return requiredDebt The required additional debt amount in value terms.
    2398
       */
    2399
      function _getRequiredDebtForGtHf(
    2400
        ISpoke spoke,
    2401
        address user,
    2402
        uint256 desiredHf
    2403
      ) internal view returns (uint256) {
    2404
        ISpoke.UserAccountData memory userAccountData = spoke.getUserAccountData(user);
    2405
    2406
        return
    2407
          userAccountData
    2408
            .totalCollateralValue
    2409
            .percentMulDown(userAccountData.avgCollateralFactor.fromWadDown())
    2410
            .percentMulDown(99_00)
    2411
            .wadDivDown(desiredHf) - userAccountData.totalDebtValue;
    2412
        // buffer to force debt lower (ie making sure resultant debt creates HF that is gt desired)
    2413
      }
    2414
    2415
      /// @dev Borrow to be below a certain healthy health factor
    2416
      /// @dev This function validates HF and does not mock price, thus it will cache user RP properly
    2417
      function _borrowToBeAboveHealthyHf(
    2418
        ISpoke spoke,
    2419
        address user,
    2420
        uint256 reserveId,
    2421
        uint256 desiredHf
    2422
      ) internal returns (uint256, uint256) {
    2423
        uint256 requiredDebtInBase = _getRequiredDebtForGtHf(spoke, user, desiredHf);
    2424
        uint256 requiredDebtAmount = _convertValueToAmount(spoke, reserveId, requiredDebtInBase) - 1;
    2425
    2426
        vm.assume(requiredDebtAmount < MAX_SUPPLY_AMOUNT);
    2427
    2428
        vm.prank(user);
    2429
        spoke.borrow(reserveId, requiredDebtAmount, user);
    2430
    2431
        uint256 finalHf = _getUserHealthFactor(spoke, user);
    2432
        assertGt(finalHf, desiredHf, 'should borrow so that HF is above desiredHf');
    2433
        return (finalHf, requiredDebtAmount);
    2434
      }
    2435
    2436 0
      function _mockDecimals(address underlying, uint8 decimals) internal {
    2437 0
        vm.mockCall(
    2438 0
          underlying,
    2439 0
          abi.encodeWithSelector(IERC20Metadata.decimals.selector),
    2440 0
          abi.encode(decimals)
    2441
        );
    2442
      }
    2443
    2444 0
      function _mockInterestRateBps(uint256 interestRateBps) internal {
    2445 0
        _mockInterestRateBps(address(irStrategy), interestRateBps);
    2446
      }
    2447
    2448 0
      function _mockInterestRateBps(address interestRateStrategy, uint256 interestRateBps) internal {
    2449 0
        vm.mockCall(
    2450 0
          interestRateStrategy,
    2451
          IBasicInterestRateStrategy.calculateInterestRate.selector,
    2452 0
          abi.encode(interestRateBps.bpsToRay())
    2453
        );
    2454
      }
    2455
    2456
      function _mockInterestRateBps(
    2457
        uint256 interestRateBps,
    2458
        uint256 assetId,
    2459
        uint256 liquidity,
    2460
        uint256 drawn,
    2461
        uint256 deficit,
    2462
        uint256 swept
    2463
      ) internal {
    2464
        _mockInterestRateBps(
    2465
          address(irStrategy),
    2466
          interestRateBps,
    2467
          assetId,
    2468
          liquidity,
    2469
          drawn,
    2470
          deficit,
    2471
          swept
    2472
        );
    2473
      }
    2474
    2475
      function _mockInterestRateBps(
    2476
        address interestRateStrategy,
    2477
        uint256 interestRateBps,
    2478
        uint256 assetId,
    2479
        uint256 liquidity,
    2480
        uint256 drawn,
    2481
        uint256 deficit,
    2482
        uint256 swept
    2483
      ) internal {
    2484
        vm.mockCall(
    2485
          interestRateStrategy,
    2486
          abi.encodeCall(
    2487
            IBasicInterestRateStrategy.calculateInterestRate,
    2488
            (assetId, liquidity, drawn, deficit, swept)
    2489
          ),
    2490
          abi.encode(interestRateBps.bpsToRay())
    2491
        );
    2492
      }
    2493
    2494 0
      function _mockInterestRateRay(uint256 interestRateRay) internal {
    2495 0
        _mockInterestRateRay(address(irStrategy), interestRateRay);
    2496
      }
    2497
    2498 0
      function _mockInterestRateRay(address interestRateStrategy, uint256 interestRateRay) internal {
    2499 0
        vm.mockCall(
    2500 0
          interestRateStrategy,
    2501 0
          IBasicInterestRateStrategy.calculateInterestRate.selector,
    2502 0
          abi.encode(interestRateRay)
    2503
        );
    2504
      }
    2505
    2506 0
      function _mockInterestRateRay(
    2507
        uint256 interestRateRay,
    2508
        uint256 assetId,
    2509
        uint256 liquidity,
    2510
        uint256 drawn
    2511
      ) internal {
    2512 0
        _mockInterestRateRay(address(irStrategy), interestRateRay, assetId, liquidity, drawn, 0, 0);
    2513
      }
    2514
    2515 0
      function _mockInterestRateRay(
    2516
        address interestRateStrategy,
    2517
        uint256 interestRateRay,
    2518
        uint256 assetId,
    2519
        uint256 liquidity,
    2520
        uint256 drawn,
    2521
        uint256 deficit,
    2522
        uint256 swept
    2523
      ) internal {
    2524 0
        vm.mockCall(
    2525 0
          interestRateStrategy,
    2526 0
          abi.encodeCall(
    2527
            IBasicInterestRateStrategy.calculateInterestRate,
    2528
            (assetId, liquidity, drawn, deficit, swept)
    2529
          ),
    2530 0
          abi.encode(interestRateRay)
    2531
        );
    2532
      }
    2533
    2534 0
      function _mockReservePrice(ISpoke spoke, uint256 reserveId, uint256 price) internal {
    2535 0
        require(price > 0, 'mockReservePrice: price must be positive');
    2536 0
        AaveOracle oracle = AaveOracle(spoke.ORACLE());
    2537 0
        address mockPriceFeed = address(
    2538 0
          new MockPriceFeed(oracle.DECIMALS(), oracle.DESCRIPTION(), price)
    2539
        );
    2540 0
        vm.prank(address(ADMIN));
    2541 0
        spoke.updateReservePriceSource(reserveId, mockPriceFeed);
    2542
      }
    2543
    2544 0
      function _mockReservePriceByPercent(
    2545
        ISpoke spoke,
    2546
        uint256 reserveId,
    2547
        uint256 percentage
    2548
      ) internal {
    2549 0
        uint256 initialPrice = IPriceOracle(spoke.ORACLE()).getReservePrice(reserveId);
    2550 0
        uint256 newPrice = initialPrice.percentMulDown(percentage);
    2551 0
        _mockReservePrice(spoke, reserveId, newPrice);
    2552
      }
    2553
    2554
      function _deployMockPriceFeed(ISpoke spoke, uint256 price) internal returns (address) {
    2555 61×
        AaveOracle oracle = AaveOracle(spoke.ORACLE());
    2556 149×
        return address(new MockPriceFeed(oracle.DECIMALS(), oracle.DESCRIPTION(), price));
    2557
      }
    2558
    2559 0
      function _assertBorrowRateSynced(
    2560
        IHub targetHub,
    2561
        uint256 assetId,
    2562
        string memory operation
    2563 0
      ) internal view {
    2564 0
        IHub.Asset memory asset = targetHub.getAsset(assetId);
    2565 0
        (uint256 drawn, ) = hub1.getAssetOwed(assetId);
    2566
    2567 0
        vm.assertEq(
    2568 0
          asset.drawnRate,
    2569 0
          IBasicInterestRateStrategy(asset.irStrategy).calculateInterestRate(
    2570
            assetId,
    2571 0
            asset.liquidity,
    2572
            drawn,
    2573 0
            asset.deficitRay,
    2574 0
            asset.swept
    2575
          ),
    2576 0
          string.concat('base borrow rate after ', operation)
    2577
        );
    2578
      }
    2579
    2580 0
      function _assertHubLiquidity(IHub targetHub, uint256 assetId, string memory label) internal view {
    2581 0
        IHub.Asset memory asset = targetHub.getAsset(assetId);
    2582 0
        uint256 currentHubBalance = IERC20(asset.underlying).balanceOf(address(targetHub));
    2583 0
        assertEq(
    2584 0
          targetHub.getAssetLiquidity(assetId),
    2585 0
          currentHubBalance,
    2586 0
          string.concat('hub liquidity ', label)
    2587
        );
    2588
      }
    2589
    2590 0
      function _assertEventNotEmitted(bytes32 eventSignature) internal {
    2591 0
        Vm.Log[] memory entries = vm.getRecordedLogs();
    2592 0
        for (uint256 i; i < entries.length; i++) {
    2593 0
          assertNotEq(entries[i].topics[0], eventSignature);
    2594
        }
    2595 0
        vm.recordLogs();
    2596
      }
    2597
    2598 0
      function _assertEventsNotEmitted(bytes32 event1Sig, bytes32 event2Sig) internal {
    2599 0
        Vm.Log[] memory entries = vm.getRecordedLogs();
    2600 0
        for (uint256 i; i < entries.length; i++) {
    2601 0
          assertNotEq(entries[i].topics[0], event1Sig);
    2602 0
          assertNotEq(entries[i].topics[0], event2Sig);
    2603
        }
    2604 0
        vm.recordLogs();
    2605
      }
    2606
    2607 0
      function _assertEventsNotEmitted(
    2608
        bytes32 event1Sig,
    2609
        bytes32 event2Sig,
    2610
        bytes32 event3Sig
    2611 0
      ) internal {
    2612 0
        Vm.Log[] memory entries = vm.getRecordedLogs();
    2613 0
        for (uint256 i; i < entries.length; i++) {
    2614 0
          assertNotEq(entries[i].topics[0], event1Sig);
    2615 0
          assertNotEq(entries[i].topics[0], event2Sig);
    2616 0
          assertNotEq(entries[i].topics[0], event3Sig);
    2617
        }
    2618 0
        vm.recordLogs();
    2619
      }
    2620
    2621 0
      function _assertDynamicConfigRefreshEventsNotEmitted() internal {
    2622 0
        _assertEventsNotEmitted(
    2623
          ISpoke.RefreshAllUserDynamicConfig.selector,
    2624 0
          ISpoke.RefreshSingleUserDynamicConfig.selector
    2625
        );
    2626
      }
    2627
    2628
      // @dev Helper function to get asset position, valid if no time has passed since last action
    2629 0
      function getAssetPosition(
    2630
        IHub hub,
    2631
        uint256 assetId
    2632 0
      ) internal view returns (AssetPosition memory) {
    2633 0
        IHub.Asset memory assetData = hub.getAsset(assetId);
    2634 0
        (uint256 drawn, uint256 premium) = hub.getAssetOwed(assetId);
    2635 0
        return
    2636 0
          AssetPosition({
    2637 0
            assetId: assetId,
    2638 0
            liquidity: assetData.liquidity,
    2639 0
            addedShares: assetData.addedShares,
    2640 0
            addedAmount: hub.getAddedAssets(assetId) - _calculateBurntInterest(hub, assetId),
    2641 0
            drawnShares: assetData.drawnShares,
    2642 0
            drawn: drawn,
    2643 0
            premiumShares: assetData.premiumShares,
    2644 0
            premiumOffsetRay: assetData.premiumOffsetRay,
    2645 0
            premium: premium,
    2646 0
            lastUpdateTimestamp: assetData.lastUpdateTimestamp.toUint40(),
    2647 0
            drawnIndex: assetData.drawnIndex,
    2648 0
            drawnRate: assetData.drawnRate
    2649
          });
    2650
      }
    2651
    2652 0
      function getSpokePosition(
    2653
        ISpoke spoke,
    2654
        function(ISpoke) internal view returns (uint256) reserveIdFn
    2655 0
      ) internal view returns (SpokePosition memory) {
    2656 0
        return getSpokePosition(spoke, reserveIdFn(spoke));
    2657
      }
    2658
    2659 0
      function getSpokePosition(
    2660
        ISpoke spoke,
    2661
        uint256 reserveId
    2662 0
      ) internal view returns (SpokePosition memory) {
    2663 0
        uint256 assetId = spoke.getReserve(reserveId).assetId;
    2664 0
        IHub.SpokeData memory spokeData = hub1.getSpoke(assetId, address(spoke));
    2665 0
        (uint256 drawn, uint256 premium) = hub1.getSpokeOwed(assetId, address(spoke));
    2666 0
        return
    2667 0
          SpokePosition({
    2668
            reserveId: reserveId,
    2669 0
            assetId: assetId,
    2670 0
            addedShares: spokeData.addedShares,
    2671 0
            addedAmount: hub1.getSpokeAddedAssets(assetId, address(spoke)),
    2672 0
            drawnShares: spokeData.drawnShares,
    2673 0
            drawn: drawn,
    2674 0
            premiumShares: spokeData.premiumShares,
    2675 0
            premiumOffsetRay: spokeData.premiumOffsetRay,
    2676 0
            premium: premium
    2677
          });
    2678
      }
    2679
    2680
      function _getReserve(ISpoke spoke, uint256 reserveId) internal view returns (Reserve memory) {
    2681
        ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
    2682
        return
    2683
          Reserve({
    2684
            reserveId: reserveId,
    2685
            hub: _hub(spoke, reserveId),
    2686
            assetId: reserve.assetId,
    2687
            decimals: reserve.decimals,
    2688
            dynamicConfigKey: reserve.dynamicConfigKey,
    2689
            paused: reserve.flags.paused(),
    2690
            frozen: reserve.flags.frozen(),
    2691
            borrowable: reserve.flags.borrowable(),
    2692
            receiveSharesEnabled: reserve.flags.receiveSharesEnabled(),
    2693
            collateralRisk: reserve.collateralRisk
    2694
          });
    2695
      }
    2696
    2697 0
      function assertEq(SpokePosition memory a, AssetPosition memory b) internal pure {
    2698 0
        assertEq(a.assetId, b.assetId, 'assetId');
    2699 0
        assertEq(a.addedShares, b.addedShares, 'addedShares');
    2700 0
        assertEq(a.addedAmount, b.addedAmount, 'addedAmount');
    2701 0
        assertEq(a.drawnShares, b.drawnShares, 'drawnShares');
    2702 0
        assertEq(a.drawn, b.drawn, 'drawnDebt');
    2703 0
        assertEq(a.premiumShares, b.premiumShares, 'premiumShares');
    2704 0
        assertEq(a.premiumOffsetRay, b.premiumOffsetRay, 'premiumOffsetRay');
    2705 0
        assertEq(a.premium, b.premium, 'premium');
    2706
      }
    2707
    2708 0
      function assertEq(SpokePosition memory a, SpokePosition memory b) internal pure {
    2709 0
        assertEq(a.reserveId, b.reserveId, 'reserveId');
    2710 0
        assertEq(a.assetId, b.assetId, 'assetId');
    2711 0
        assertEq(a.addedShares, b.addedShares, 'addedShares');
    2712 0
        assertEq(a.addedAmount, b.addedAmount, 'addedAmount');
    2713 0
        assertEq(a.drawnShares, b.drawnShares, 'drawnShares');
    2714 0
        assertEq(a.drawn, b.drawn, 'drawn');
    2715 0
        assertEq(a.premiumShares, b.premiumShares, 'premiumShares');
    2716 0
        assertEq(a.premiumOffsetRay, b.premiumOffsetRay, 'premiumOffsetRay');
    2717 0
        assertEq(a.premium, b.premium, 'premium');
    2718 0
        assertEq(abi.encode(a), abi.encode(b)); // sanity check
    2719
      }
    2720
    2721 0
      modifier pausePrank() {
    2722 0
        (VmSafe.CallerMode callerMode, address msgSender, address txOrigin) = vm.readCallers();
    2723 0
        if (callerMode == VmSafe.CallerMode.RecurrentPrank) vm.stopPrank();
    2724
        _;
    2725 0
        if (callerMode == VmSafe.CallerMode.RecurrentPrank) vm.startPrank(msgSender, txOrigin);
    2726
      }
    2727
    2728 0
      function makeEntity(string memory id, bytes32 key) internal returns (address) {
    2729 0
        return makeAddr(string.concat(id, '-', vm.toString(uint256(key))));
    2730
      }
    2731
    2732 0
      function makeUser(uint256 i) internal returns (address) {
    2733 0
        return makeEntity('user', bytes32(i));
    2734
      }
    2735
    2736 0
      function makeUser() internal returns (address) {
    2737 0
        return makeEntity('user', vm.randomBytes8());
    2738
      }
    2739
    2740
      function makeSpoke() internal returns (address) {
    2741
        return makeEntity('spoke', vm.randomBytes8());
    2742
      }
    2743
    2744 0
      function _getTypedDataHash(
    2745
        TestnetERC20 token,
    2746
        EIP712Types.Permit memory permit
    2747 0
      ) internal view returns (bytes32) {
    2748 0
        return
    2749 0
          keccak256(
    2750 0
            abi.encodePacked(
    2751
              '\x19\x01',
    2752 0
              token.DOMAIN_SEPARATOR(),
    2753 0
              vm.eip712HashStruct('Permit', abi.encode(permit))
    2754
            )
    2755
          );
    2756
      }
    2757
    2758 0
      function _getTypedDataHash(
    2759
        ISpoke spoke,
    2760
        EIP712Types.SetUserPositionManager memory setUserPositionManager
    2761 0
      ) internal view returns (bytes32) {
    2762 0
        return
    2763 0
          keccak256(
    2764 0
            abi.encodePacked(
    2765
              '\x19\x01',
    2766 0
              spoke.DOMAIN_SEPARATOR(),
    2767 0
              vm.eip712HashStruct('SetUserPositionManager', abi.encode(setUserPositionManager))
    2768
            )
    2769
          );
    2770
      }
    2771
    2772
      /**
    2773
       * @dev Warps after to a random time after a randomly generated deadline.
    2774
       * @return The randomly generated deadline.
    2775
       */
    2776 0
      function _warpAfterRandomDeadline() internal returns (uint256) {
    2777 0
        uint256 deadline = vm.randomUint(0, MAX_SKIP_TIME - 1);
    2778 0
        vm.warp(vm.randomUint(deadline + 1, MAX_SKIP_TIME));
    2779 0
        return deadline;
    2780
      }
    2781
    2782
      /**
    2783
       * @dev Warps to a random time before a randomly generated deadline.
    2784
       * @return The randomly generated deadline.
    2785
       */
    2786 0
      function _warpBeforeRandomDeadline() internal returns (uint256) {
    2787 0
        uint256 deadline = vm.randomUint(1, MAX_SKIP_TIME);
    2788 0
        vm.warp(vm.randomUint(0, deadline - 1));
    2789 0
        return deadline;
    2790
      }
    2791
    2792
      /**
    2793
       * @dev Burns random nonces from 1 at the specified key lifetime.
    2794
       */
    2795 0
      function _burnRandomNoncesAtKey(
    2796
        INoncesKeyed verifier,
    2797
        address user,
    2798
        uint192 key
    2799 0
      ) internal returns (uint256) {
    2800 0
        uint256 currentKeyNonce = verifier.nonces(user, key);
    2801 0
        (, uint64 nonce) = _unpackNonce(currentKeyNonce);
    2802
    2803 0
        uint64 toBurn = vm.randomUint(1, 100).toUint64();
    2804 0
        for (uint256 i; i < toBurn; ++i) {
    2805 0
          vm.prank(user);
    2806 0
          verifier.useNonce(key);
    2807
        }
    2808 0
        uint256 newKeyNonce = _packNonce(key, nonce + toBurn);
    2809
    2810
        // doesn't work because of the assumption in StdStorage.checkSlotMutatesCall :(
    2811
        // stdstore
    2812
        //   .target(verifier)
    2813
        //   .sig(INoncesKeyed.nonces.selector)
    2814
        //   .with_key(user)
    2815
        //   .with_key(key)
    2816
        //   .checked_write(newNonce);
    2817
    2818 0
        assertEq(verifier.nonces(user, key), newKeyNonce);
    2819 0
        return newKeyNonce;
    2820
      }
    2821
    2822 0
      function _burnRandomNoncesAtKey(INoncesKeyed verifier, address user) internal returns (uint256) {
    2823 0
        return _burnRandomNoncesAtKey(verifier, user, _randomNonceKey());
    2824
      }
    2825
    2826 0
      function _getRandomInvalidNonceAtKey(
    2827
        INoncesKeyed verifier,
    2828
        address user,
    2829
        uint192 key
    2830 0
      ) internal returns (uint256) {
    2831 0
        (uint192 currentKey, uint64 currentNonce) = _unpackNonce(verifier.nonces(user, key));
    2832 0
        assertEq(currentKey, key);
    2833 0
        uint64 nonce = _randomNonce();
    2834 0
        while (currentNonce == nonce) nonce = _randomNonce();
    2835 0
        return _packNonce(key, nonce);
    2836
      }
    2837
    2838 0
      function _assertNonceIncrement(
    2839
        INoncesKeyed verifier,
    2840
        address who,
    2841
        uint256 prevKeyNonce
    2842
      ) internal view {
    2843
        (uint192 nonceKey, uint64 nonce) = _unpackNonce(prevKeyNonce);
    2844
        // prettier-ignore
    2845 0
        unchecked { ++nonce; }
    2846 0
        assertEq(verifier.nonces(who, nonceKey), _packNonce(nonceKey, nonce));
    2847
      }
    2848
    2849
      /// @dev Pack key and nonce into a keyNonce
    2850 0
      function _packNonce(uint192 key, uint64 nonce) internal pure returns (uint256) {
    2851 0
        return (uint256(key) << 64) | nonce;
    2852
      }
    2853
    2854
      /// @dev Unpack a keyNonce into its key and nonce components
    2855 0
      function _unpackNonce(uint256 keyNonce) internal pure returns (uint192 key, uint64 nonce) {
    2856 0
        return (uint192(keyNonce >> 64), uint64(keyNonce));
    2857
      }
    2858
    2859 0
      function _bpsToRay(uint256 bps) internal pure returns (uint256) {
    2860 0
        return (bps * WadRayMath.RAY) / PercentageMath.PERCENTAGE_FACTOR;
    2861
      }
    2862
    2863
      /// @dev Calculate expected fees based on previous drawn index
    2864 0
      function _calcUnrealizedFees(IHub hub, uint256 assetId) internal view returns (uint256) {
    2865 0
        IHub.Asset memory asset = hub.getAsset(assetId);
    2866 0
        uint256 previousIndex = asset.drawnIndex;
    2867 0
        uint256 drawnIndex = asset.drawnIndex.rayMulUp(
    2868 0
          MathUtils.calculateLinearInterest(asset.drawnRate, uint40(asset.lastUpdateTimestamp))
    2869
        );
    2870
    2871 0
        uint256 aggregatedOwedRayAfter = (((uint256(asset.drawnShares) + asset.premiumShares) *
    2872 0
          drawnIndex).toInt256() - asset.premiumOffsetRay).toUint256() + asset.deficitRay;
    2873 0
        uint256 aggregatedOwedRayBefore = (((uint256(asset.drawnShares) + asset.premiumShares) *
    2874 0
          previousIndex).toInt256() - asset.premiumOffsetRay).toUint256() + asset.deficitRay;
    2875
    2876 0
        return
    2877 0
          (aggregatedOwedRayAfter.fromRayUp() - aggregatedOwedRayBefore.fromRayUp()).percentMulDown(
    2878 0
            asset.liquidityFee
    2879
          );
    2880
      }
    2881
    2882 0
      function _getExpectedFeeReceiverAddedAssets(
    2883
        IHub hub,
    2884
        uint256 assetId
    2885 0
      ) internal view returns (uint256) {
    2886 0
        uint256 expectedFees = hub.getAsset(assetId).realizedFees + _calcUnrealizedFees(hub, assetId);
    2887 0
        assertEq(expectedFees, hub.getAssetAccruedFees(assetId), 'asset accrued fees');
    2888 0
        return hub.getSpokeAddedAssets(assetId, hub.getAsset(assetId).feeReceiver) + expectedFees;
    2889
      }
    2890
    2891 0
      function _getAddedAssetsWithFees(IHub hub, uint256 assetId) internal view returns (uint256) {
    2892 0
        return
    2893 0
          hub.getAddedAssets(assetId) +
    2894 0
          hub.getAsset(assetId).realizedFees +
    2895 0
          _calcUnrealizedFees(hub, assetId);
    2896
      }
    2897
    }
    15% tests/Constants.sol
    Lines covered: 2 / 13 (15%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.0;
    4
    5 0
    library Constants {
    6
      /// @dev Hub Constants
    7 0
      uint8 public constant MAX_ALLOWED_UNDERLYING_DECIMALS = 18;
    8 0
      uint8 public constant MIN_ALLOWED_UNDERLYING_DECIMALS = 6;
    9
      uint40 public constant MAX_ALLOWED_SPOKE_CAP = type(uint40).max;
    10 0
      uint24 public constant MAX_RISK_PREMIUM_THRESHOLD = type(uint24).max; // 167772.15%
    11
    12
      /// @dev Spoke Constants
    13 0
      uint8 public constant ORACLE_DECIMALS = 8;
    14 0
      uint64 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
    15 0
      uint256 public constant DUST_LIQUIDATION_THRESHOLD = 1000e26;
    16
      uint24 public constant MAX_ALLOWED_COLLATERAL_RISK = 1000_00; // 1000.00%
    17 0
      uint256 public constant MAX_ALLOWED_DYNAMIC_CONFIG_KEY = type(uint24).max;
    18 0
      bytes32 public constant SET_USER_POSITION_MANAGER_TYPEHASH =
    19
        // keccak256('SetUserPositionManager(address positionManager,address user,bool approve,uint256 nonce,uint256 deadline)')
    20 0
        0x758d23a3c07218b7ea0b4f7f63903c4e9d5cbde72d3bcfe3e9896639025a0214;
    21 0
      uint256 public constant MAX_ALLOWED_ASSET_ID = type(uint16).max;
    22
    }
    66% tests/mocks/MockPriceFeed.sol
    Lines covered: 10 / 15 (66%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.0;
    4
    5
    import {AggregatorV3Interface} from 'src/dependencies/chainlink/AggregatorV3Interface.sol';
    6
    7 205×
    contract MockPriceFeed is AggregatorV3Interface {
    8 32×
      uint8 public immutable override decimals;
    9 0
      string public override description;
    10
    11
      int256 private immutable _price;
    12
    13
      error OperationNotSupported();
    14
    15 27×
      constructor(uint8 decimals_, string memory description_, uint256 price_) {
    16
        decimals = decimals_;
    17
        description = description_;
    18
        _price = int256(price_);
    19
      }
    20
    21 0
      function version() external pure override returns (uint256) {
    22 0
        return 1;
    23
      }
    24
    25 15×
      function getRoundData(
    26
        uint80
    27 0
      ) external pure override returns (uint80, int256, uint256, uint256, uint80) {
    28 0
        revert OperationNotSupported();
    29
      }
    30
    31
      function latestRoundData()
    32
        external
    33
        view
    34
        virtual
    35
        override
    36
        returns (
    37
          uint80 roundId,
    38
          int256 answer,
    39
          uint256 startedAt,
    40
          uint256 updatedAt,
    41
          uint80 answeredInRound
    42
        )
    43
      {
    44 12×
        roundId = uint80(block.timestamp);
    45
        answer = _price;
    46
        startedAt = block.timestamp;
    47
        updatedAt = block.timestamp;
    48
        answeredInRound = roundId;
    49
      }
    50
    }
    33% tests/mocks/TestnetERC20.sol
    Lines covered: 12 / 36 (33%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // Copyright (c) 2025 Aave Labs
    3
    pragma solidity ^0.8.0;
    4
    5
    import {ERC20} from 'src/dependencies/openzeppelin/ERC20.sol';
    6
    import {IERC20Permit} from 'src/dependencies/openzeppelin/IERC20Permit.sol';
    7
    8
    /**
    9
     * @title TestnetERC20
    10
     * @dev ERC20 minting logic
    11
     */
    12 229×
    contract TestnetERC20 is IERC20Permit, ERC20 {
    13 15×
      bytes public constant EIP712_REVISION = bytes('1');
    14
      bytes32 internal constant EIP712_DOMAIN =
    15
        keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)');
    16 0
      bytes32 public constant PERMIT_TYPEHASH =
    17 0
        keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
    18
    19
      // Map of address nonces (address => nonce)
    20
      mapping(address => uint256) internal _nonces;
    21
    22 0
      bytes32 public DOMAIN_SEPARATOR;
    23
    24
      uint8 private _decimals;
    25
    26 34×
      constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
    27
        uint256 chainId = block.chainid;
    28
    29
        DOMAIN_SEPARATOR = keccak256(
    30 18×
          abi.encode(
    31
            EIP712_DOMAIN,
    32 10×
            keccak256(bytes(name_)),
    33
            keccak256(EIP712_REVISION),
    34
            chainId,
    35
            address(this)
    36
          )
    37
        );
    38
        _setupDecimals(decimals_);
    39
      }
    40
    41
      /// @inheritdoc IERC20Permit
    42 0
      function permit(
    43
        address owner,
    44
        address spender,
    45
        uint256 value,
    46
        uint256 deadline,
    47
        uint8 v,
    48
        bytes32 r,
    49
        bytes32 s
    50 0
      ) external override {
    51
        require(owner != address(0), 'INVALID_OWNER');
    52
        //solium-disable-next-line
    53 0
        require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
    54 0
        uint256 currentValidNonce = _nonces[owner];
    55 0
        bytes32 digest = keccak256(
    56 0
          abi.encodePacked(
    57
            '\x19\x01',
    58 0
            DOMAIN_SEPARATOR,
    59 0
            keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
    60
          )
    61
        );
    62 0
        require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
    63 0
        _nonces[owner] = currentValidNonce + 1;
    64 0
        _approve(owner, spender, value);
    65
      }
    66
    67
      /**
    68
       * @dev Function to mint tokens
    69
       * @param value The amount of tokens to mint.
    70
       * @return A boolean that indicates if the operation was successful.
    71
       */
    72 0
      function mint(uint256 value) public virtual returns (bool) {
    73 0
        _mint(_msgSender(), value);
    74 0
        return true;
    75
      }
    76
    77
      /**
    78
       * @dev Function to mint tokens to address
    79
       * @param account The account to mint tokens.
    80
       * @param value The amount of tokens to mint.
    81
       * @return A boolean that indicates if the operation was successful.
    82
       */
    83 0
      function mint(address account, uint256 value) public virtual returns (bool) {
    84 0
        _mint(account, value);
    85 0
        return true;
    86
      }
    87
    88 0
      function nonces(address owner) public view returns (uint256) {
    89 0
        return _nonces[owner];
    90
      }
    91
    92 0
      function decimals() public view virtual override returns (uint8) {
    93 0
        return _decimals;
    94
      }
    95
    96
      /**
    97
       * @dev Sets {decimals} to a value other than the default one of 18.
    98
       *
    99
       * WARNING: This function should only be called from the constructor. Most
    100
       * applications that interact with token contracts will not expect
    101
       * {decimals} to ever change, and may work incorrectly if it does.
    102
       */
    103
      function _setupDecimals(uint8 decimals_) internal {
    104 12×
        _decimals = decimals_;
    105
      }
    106
    }
    100% tests/recon/BeforeAfter.sol
    Lines covered: 32 / 32 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Setup} from "./Setup.sol";
    5
    import {ISpoke} from "src/spoke/interfaces/ISpoke.sol";
    6
    import {LiquidationLogic} from "src/spoke/libraries/LiquidationLogic.sol";
    7
    8
    9
    // ghost variables for tracking state variable values before and after function calls
    10
    abstract contract BeforeAfter is Setup {
    11
        enum Operation {
    12
            None,
    13
            SetPrice
    14
        }
    15
    16
        struct Vars {
    17
            bool isAnyUserLiquidatable;
    18
            Operation operation;
    19
        }
    20
    21
        Vars internal _before;
    22
        Vars internal _after;
    23
    24
        mapping(uint256 assetId => uint256 maxDrawnIndexSeen) internal ghost_maxDrawnIndexSeen;
    25
        mapping(uint256 assetId => uint256 maxSupplySharePriceAssetsSeen) internal ghost_maxSupplySharePriceAssetsSeen;
    26
        mapping(uint256 assetId => uint256 maxSupplySharePriceSharesSeen) internal ghost_maxSupplySharePriceSharesSeen;
    27
    28
        /// === MODIFIERS === ///
    29
        /// Prank admin and actor
    30
        
    31
        modifier asAdmin {
    32 423×
            vm.startPrank(ADMIN);
    33 36×
            __before();
    34
            _;
    35 26×
            __after();
    36 134×
            vm.stopPrank();
    37
        }
    38
    39
        modifier asActor {
    40 416×
            vm.startPrank(address(_getActor()));
    41 32×
            __before();
    42
            _;
    43 11×
            __after();
    44 86×
            vm.stopPrank();
    45
        }
    46
    47
        function __before() internal {
    48
            __snapshot(_before);
    49
        }
    50
    51
        function __after() internal {
    52
            __snapshot(_after);
    53
        }
    54
    55
        function __snapshot(Vars storage vars) internal {
    56
            vars.isAnyUserLiquidatable = false;
    57
            vars.operation = Operation.None;
    58
    59
            address[] memory actors = _getActors();
    60 12×
            for(uint256 i = 0; i < actors.length; i++) {
    61 24×
                address actor = actors[i];
    62 67×
                ISpoke.UserAccountData memory actorAccountData = iSpoke.getUserAccountData(actor);
    63 21×
                vars.isAnyUserLiquidatable = vars.isAnyUserLiquidatable || (actorAccountData.healthFactor < LiquidationLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD);
    64
            }
    65
        }
    66
    67
        function _updateMonotonicGhosts(uint256 assetId) internal {
    68 61×
            uint256 currentDrawnIndex = iHub.getAssetDrawnIndex(assetId);
    69 17×
            if (currentDrawnIndex > ghost_maxDrawnIndexSeen[assetId]) {
    70 13×
                ghost_maxDrawnIndexSeen[assetId] = currentDrawnIndex;
    71
            }
    72
    73 60×
            uint256 addedShares = iHub.getAddedShares(assetId);
    74
            if (addedShares > 0) {
    75 61×
                uint256 addedAssets = iHub.getAddedAssets(assetId);
    76 15×
                uint256 maxAssets = ghost_maxSupplySharePriceAssetsSeen[assetId];
    77
                uint256 maxShares = ghost_maxSupplySharePriceSharesSeen[assetId];
    78 23×
                if (maxShares == 0 || maxAssets * addedShares <= addedAssets * maxShares) {
    79 17×
                    ghost_maxSupplySharePriceAssetsSeen[assetId] = addedAssets;
    80 10×
                    ghost_maxSupplySharePriceSharesSeen[assetId] = addedShares;
    81
                }
    82
            }
    83
        }
    84
    }
    100% tests/recon/CryticTester.sol
    Lines covered: 2 / 2 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {CryticAsserts} from "@chimera/CryticAsserts.sol";
    5
    6
    import {TargetFunctions} from "./TargetFunctions.sol";
    7
    8
    // echidna tests/recon/CryticTester.sol --contract CryticTester --config echidna.yaml --format text --workers 32 --test-limit 1000000000
    9
    // medusa fuzz
    10 378×
    contract CryticTester is TargetFunctions, CryticAsserts {
    11
        constructor() payable {
    12
            setup();
    13
        }
    14
    }
    97% tests/recon/Properties.sol
    Lines covered: 141 / 144 (97%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Asserts} from "@chimera/Asserts.sol";
    5
    import {BeforeAfter} from "./BeforeAfter.sol";
    6
    7
    /// @notice Invariants are written to support both Echidna property mode (bool) and optimization mode (int256).
    8
    /// @dev Default is property mode; to switch, run:
    9
    ///      `sed -i '' -e 's/OPTIMIZATION_MODE = false/OPTIMIZATION_MODE = true/' -e 's/public returns (bool)/public returns (int256 maxViolation)/g' -e 's/return maxViolation <= 0;/return maxViolation;/g' tests/recon/Properties.sol`
    10
    ///      This keeps the same invariant logic but changes the return type and value so fuzzers can optimize on a
    11
    ///      signed percentage target where <= 0 is success and > 0 is a violation, while skipping assertions.
    12
    abstract contract Properties is BeforeAfter, Asserts {
    13
        int256 internal constant PERCENT = int256(1e18);
    14
        bool internal constant OPTIMIZATION_MODE = false;
    15
    16
        /// @dev Avoids Low likelihood scenarios in Audit Contest
    17
        uint256 internal constant MIN_TOTAL_SUPPLIED = 1e6;
    18
    19
        string constant ASSERTION_LIQUIDATION_CALL_DOS = "!!! iSpoke_liquidationCall DoS";
    20
        string constant ASSERTION_REPAY_DOS = "!!! iSpoke_repay DoS";
    21
        string constant ASSERTION_SUPPLY_DOS = "!!! iSpoke_supply DoS";
    22
        string constant ASSERTION_WITHDRAW_DOS = "!!! iSpoke_withdraw DoS";
    23
        string constant ASSERTION_MINT_FEE_SHARES_PPS_CHANGE = "!!! iHub_mintFeeShares PPS change";
    24
        string constant ASSERTION_CANARY = "!!! canary assertion";
    25
    26
        /// @dev Avoids invalidated issues by only implementing pre-vetted list of Audit Contest invariants
    27
        /// @dev Reference https://audits.sherlock.xyz/contests/1209
    28
        string constant INVARIANT_1_TOTAL_BORROWED_LESS_THAN_SUPPLIED_V0 =
    29
            "Invariant 1: Total borrowed assets <= total supplied assets (v0)";
    30
        string constant INVARIANT_1_TOTAL_BORROWED_LESS_THAN_SUPPLIED_V1 =
    31
            "Invariant 1: Total borrowed assets <= total supplied assets (v1)";
    32
        string constant INVARIANT_1_TOTAL_BORROWED_LESS_THAN_SUPPLIED_V2 =
    33
            "Invariant 1: Total borrowed assets <= total supplied assets (v2)";
    34
        string constant INVARIANT_2_TOTAL_BORROWED_SHARES_MATCHES_SPOKE_SUM =
    35
            "Invariant 2: Total borrowed shares == sum of Spoke debt shares";
    36
        string constant INVARIANT_3_HUB_ADDED_ASSETS_GREATER_THAN_SPOKE_SUM =
    37
            "Invariant 3: Hub added assets >= sum of Spoke added assets (converted from shares)";
    38
        string constant INVARIANT_4_HUB_ADDED_SHARES_MATCHES_SPOKE_SUM =
    39
            "Invariant 4: Hub added shares == sum of Spoke added shares";
    40
        string constant INVARIANT_5_SUPPLY_SHARE_PRICE_AND_DRAWN_INDEX_MONOTONIC =
    41
            "Invariant 5: Supply share price and drawn index cannot decrease";
    42
    43
        /// @dev Add extra 'Holy grail' property
    44
        string constant INVARIANT_HOLY_GRAIL_SHOULD_NOT_BECOME_LIQUIDATABLE =
    45
            "Holy grail: Users should not become liquidatable except by price change";
    46
        string constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE = "Canary invariant";
    47
    48
        function _relativeDiff(int256 diff, uint256 denom) internal pure returns (int256) {
    49
            if (denom == 0) {
    50
                if (diff == 0) {
    51
                    return 0;
    52
                }
    53 11×
                return diff > 0 ? type(int256).max : type(int256).min;
    54
            }
    55 11×
            return (diff * PERCENT) / int256(denom);
    56
        }
    57
    58
        function _abs(int256 value) internal pure returns (int256) {
    59
            return value < 0 ? -value : value;
    60
        }
    61
    62
        /// @notice Invariant 1: Total borrowed assets <= total supplied assets (v0)
    63
        /// @dev Uses definition assumed by the auditors
    64
        function invariant_totalBorrowedLessThanSupplied_v0() public returns (bool) {
    65 69×
            uint256 assetCount = iHub.getAssetCount();
    66
            int256 maxViolation = type(int256).min;
    67
    68 16×
            for (uint256 i = 0; i < assetCount; i++) {
    69 61×
                uint256 totalBorrowed = iHub.getAssetTotalOwed(i);
    70 63×
                uint256 totalSupplied = iHub.getAddedShares(i);
    71
    72 12×
                int256 diff = totalBorrowed >= totalSupplied
    73
                    ? int256(totalBorrowed - totalSupplied)
    74 11×
                    : -int256(totalSupplied - totalBorrowed);
    75
                int256 relativeDiff = _relativeDiff(diff, totalSupplied);
    76
                if (relativeDiff > maxViolation) {
    77
                    maxViolation = relativeDiff;
    78
                }
    79
            }
    80
            if (!OPTIMIZATION_MODE) {
    81 25×
                t(maxViolation <= 0, INVARIANT_1_TOTAL_BORROWED_LESS_THAN_SUPPLIED_V0);
    82
            }
    83
            return maxViolation <= 0;
    84
        }
    85
    86
        /// @notice Invariant 1: Total borrowed assets <= total supplied assets (v1)
    87
        /// @dev Uses definition provided by the protocol team
    88
        function invariant_totalBorrowedLessThanSupplied_v1() public returns (bool) {
    89 69×
            uint256 assetCount = iHub.getAssetCount();
    90
            int256 maxViolation = type(int256).min;
    91
    92 17×
            for (uint256 i = 0; i < assetCount; i++) {
    93 61×
                uint256 totalBorrowed = iHub.getAssetTotalOwed(i);
    94 190×
                uint256 totalSupplied = iHub.previewRemoveByShares(i, iHub.getAddedShares(i)) + iHub.getAssetAccruedFees(i);
    95
    96
                if (totalSupplied <= MIN_TOTAL_SUPPLIED) {
    97
                    continue;
    98
                }
    99
    100 12×
                int256 diff = totalBorrowed >= totalSupplied
    101
                    ? int256(totalBorrowed - totalSupplied)
    102 11×
                    : -int256(totalSupplied - totalBorrowed);
    103
                int256 relativeDiff = _relativeDiff(diff, totalSupplied);
    104
                if (relativeDiff > maxViolation) {
    105
                    maxViolation = relativeDiff;
    106
                }
    107
            }
    108
            if (!OPTIMIZATION_MODE) {
    109 24×
                t(maxViolation <= 0, INVARIANT_1_TOTAL_BORROWED_LESS_THAN_SUPPLIED_V1);
    110
            }
    111
            return maxViolation <= 0;
    112
        }
    113
    114
        /// @notice Invariant 1: Total borrowed assets <= total supplied assets (v2)
    115
        /// @dev Uses definition provided by the protocol team with auditors fix
    116
        function invariant_totalBorrowedLessThanSupplied_v2() public returns (bool) {
    117 69×
            uint256 assetCount = iHub.getAssetCount();
    118
            int256 maxViolation = type(int256).min;
    119
    120 17×
            for (uint256 i = 0; i < assetCount; i++) {
    121 61×
                uint256 totalBorrowed = iHub.getAssetTotalOwed(i);
    122 128×
                uint256 totalSupplied = iHub.getAddedAssets(i) + iHub.getAssetAccruedFees(i);
    123
    124
                if (totalSupplied <= MIN_TOTAL_SUPPLIED) {
    125
                    continue;
    126
                }
    127
    128 12×
                int256 diff = totalBorrowed >= totalSupplied
    129
                    ? int256(totalBorrowed - totalSupplied)
    130 11×
                    : -int256(totalSupplied - totalBorrowed);
    131
                int256 relativeDiff = _relativeDiff(diff, totalSupplied);
    132
                if (relativeDiff > maxViolation) {
    133
                    maxViolation = relativeDiff;
    134
                }
    135
            }
    136
            if (!OPTIMIZATION_MODE) {
    137 24×
                t(maxViolation <= 0, INVARIANT_1_TOTAL_BORROWED_LESS_THAN_SUPPLIED_V2);
    138
            }
    139
            return maxViolation <= 0;
    140
        }
    141
    142
        /// @notice Invariant 2: Total borrowed shares == sum of Spoke debt shares
    143
        function invariant_totalBorrowedSharesMatchesSpokeSum() public returns (bool) {
    144 69×
            uint256 assetCount = iHub.getAssetCount();
    145
            int256 maxViolation = type(int256).min;
    146
    147 16×
            for (uint256 i = 0; i < assetCount; i++) {
    148 61×
                uint256 hubDrawnShares = iHub.getAssetDrawnShares(i);
    149 62×
                uint256 spokesCount = iHub.getSpokeCount(i);
    150
    151
                uint256 sumSpokeDrawnShares = 0;
    152 13×
                for (uint256 j = 0; j < spokesCount; j++) {
    153 63×
                    address spoke = iHub.getSpokeAddress(i, j);
    154 70×
                    sumSpokeDrawnShares += iHub.getSpokeDrawnShares(i, spoke);
    155
                }
    156
    157 10×
                int256 diff = hubDrawnShares >= sumSpokeDrawnShares
    158
                    ? int256(hubDrawnShares - sumSpokeDrawnShares)
    159 0
                    : -int256(sumSpokeDrawnShares - hubDrawnShares);
    160 13×
                int256 relativeDiff = _relativeDiff(_abs(diff), sumSpokeDrawnShares);
    161
                if (relativeDiff > maxViolation) {
    162
                    maxViolation = relativeDiff;
    163
                }
    164
            }
    165
            if (!OPTIMIZATION_MODE) {
    166 24×
                t(maxViolation <= 0, INVARIANT_2_TOTAL_BORROWED_SHARES_MATCHES_SPOKE_SUM);
    167
            }
    168
            return maxViolation <= 0;
    169
        }
    170
    171
        /// @notice Invariant 3: Hub added assets >= sum of Spoke added assets (converted from shares)
    172
        function invariant_hubAddedAssetsGreaterThanSpokeSum() public returns (bool) {
    173 69×
            uint256 assetCount = iHub.getAssetCount();
    174
            int256 maxViolation = type(int256).min;
    175
    176 16×
            for (uint256 i = 0; i < assetCount; i++) {
    177 61×
                uint256 addedShares = iHub.getAddedShares(i);
    178 63×
                uint256 hubAddedAssets = iHub.previewRemoveByShares(i, addedShares);
    179 62×
                uint256 spokesCount = iHub.getSpokeCount(i);
    180
    181
                uint256 sumSpokeAddedAssets = 0;
    182 13×
                for (uint256 j = 0; j < spokesCount; j++) {
    183 63×
                    address spoke = iHub.getSpokeAddress(i, j);
    184 70×
                    sumSpokeAddedAssets += iHub.getSpokeAddedAssets(i, spoke);
    185
                }
    186
    187 12×
                int256 diff = sumSpokeAddedAssets >= hubAddedAssets
    188
                    ? int256(sumSpokeAddedAssets - hubAddedAssets)
    189 11×
                    : -int256(hubAddedAssets - sumSpokeAddedAssets);
    190
                int256 relativeDiff = _relativeDiff(diff, sumSpokeAddedAssets);
    191
                if (relativeDiff > maxViolation) {
    192
                    maxViolation = relativeDiff;
    193
                }
    194
            }
    195
            if (!OPTIMIZATION_MODE) {
    196 24×
                t(maxViolation <= 0, INVARIANT_3_HUB_ADDED_ASSETS_GREATER_THAN_SPOKE_SUM);
    197
            }
    198
            return maxViolation <= 0;
    199
        }
    200
    201
        /// @notice Invariant 4: Hub added shares == sum of Spoke added shares
    202
        function invariant_hubAddedSharesMatchesSpokeSum() public returns (bool) {
    203 69×
            uint256 assetCount = iHub.getAssetCount();
    204
            int256 maxViolation = type(int256).min;
    205
    206 16×
            for (uint256 i = 0; i < assetCount; i++) {
    207 61×
                uint256 hubAddedShares = iHub.getAddedShares(i);
    208 62×
                uint256 spokesCount = iHub.getSpokeCount(i);
    209
    210
                uint256 sumSpokeAddedShares = 0;
    211 13×
                for (uint256 j = 0; j < spokesCount; j++) {
    212 63×
                    address spoke = iHub.getSpokeAddress(i, j);
    213 70×
                    sumSpokeAddedShares += iHub.getSpokeAddedShares(i, spoke);
    214
                }
    215
    216 10×
                int256 diff = hubAddedShares >= sumSpokeAddedShares
    217
                    ? int256(hubAddedShares - sumSpokeAddedShares)
    218 0
                    : -int256(sumSpokeAddedShares - hubAddedShares);
    219
                int256 relativeDiff = _relativeDiff(_abs(diff), sumSpokeAddedShares);
    220
                if (relativeDiff > maxViolation) {
    221
                    maxViolation = relativeDiff;
    222
                }
    223
            }
    224
            if (!OPTIMIZATION_MODE) {
    225 24×
                t(maxViolation <= 0, INVARIANT_4_HUB_ADDED_SHARES_MATCHES_SPOKE_SUM);
    226
            }
    227
            return maxViolation <= 0;
    228
        }
    229
    230
        /// @notice Invariant 5: Supply share price and drawn index cannot decrease
    231
        function invariant_supplySharePriceAndDrawnIndexMonotonic() public returns (bool) {
    232 69×
            uint256 assetCount = iHub.getAssetCount();
    233
            int256 maxViolation = type(int256).min;
    234
    235 12×
            for (uint256 i = 0; i < assetCount; i++) {
    236 61×
                uint256 currentDrawnIndex = iHub.getAssetDrawnIndex(i);
    237 11×
                uint256 maxDrawnIndexSeen = ghost_maxDrawnIndexSeen[i];
    238
    239
                if (maxDrawnIndexSeen > 0) {
    240 10×
                    int256 diff = currentDrawnIndex >= maxDrawnIndexSeen
    241 11×
                        ? -int256(currentDrawnIndex - maxDrawnIndexSeen)
    242 0
                        : int256(maxDrawnIndexSeen - currentDrawnIndex);
    243
                    int256 relativeDiff = _relativeDiff(diff, maxDrawnIndexSeen);
    244
                    if (relativeDiff > maxViolation) {
    245
                        maxViolation = relativeDiff;
    246
                    }
    247
                }
    248
    249 60×
                uint256 addedShares = iHub.getAddedShares(i);
    250
                if (addedShares > 0) {
    251 61×
                    uint256 addedAssets = iHub.getAddedAssets(i);
    252 15×
                    uint256 maxAssets = ghost_maxSupplySharePriceAssetsSeen[i];
    253
                    uint256 maxShares = ghost_maxSupplySharePriceSharesSeen[i];
    254
    255
                    if (maxShares > 0) {
    256
                        uint256 lhs = addedAssets * maxShares;
    257
                        uint256 rhs = maxAssets * addedShares;
    258 29×
                        int256 diff = lhs >= rhs ? -int256(lhs - rhs) : int256(rhs - lhs);
    259
                        int256 relativeDiff = _relativeDiff(diff, rhs);
    260
                        if (relativeDiff > maxViolation) {
    261
                            maxViolation = relativeDiff;
    262
                        }
    263
                    }
    264
                }
    265
    266
                _updateMonotonicGhosts(i);
    267
            }
    268
            if (!OPTIMIZATION_MODE) {
    269 24×
                t(maxViolation <= 0, INVARIANT_5_SUPPLY_SHARE_PRICE_AND_DRAWN_INDEX_MONOTONIC);
    270
            }
    271
            return maxViolation <= 0;
    272
        }
    273
    274
        /// @dev Reference https://www.certora.com/blog/the-holy-grail
    275
        function invariant_shouldNotBecomeLiquidatable() public returns (bool) {
    276
            int256 maxViolation = type(int256).min;
    277 20×
            if (_after.operation != Operation.SetPrice) {
    278 24×
                int256 diff = (_before.isAnyUserLiquidatable || !_after.isAnyUserLiquidatable) ? int256(0) : PERCENT;
    279
                if (diff > maxViolation) {
    280
                    maxViolation = diff;
    281
                }
    282
            }
    283
            if (!OPTIMIZATION_MODE) {
    284 24×
                t(maxViolation <= 0, INVARIANT_HOLY_GRAIL_SHOULD_NOT_BECOME_LIQUIDATABLE);
    285
            }
    286
            return maxViolation <= 0;
    287
        }
    288
    289
        /// @dev Canary assertion helper. A failing input is expected to be discovered during fuzzing.
    290 15×
        function assert_canary_ASSERTION_CANARY(uint256 entropy) public {
    291 24×
            t(entropy > 0, ASSERTION_CANARY);
    292
        }
    293
    294
        /// @dev Canary global invariant expected to fail immediately.
    295 19×
        function invariant_canary() public returns (bool) {
    296
            int256 maxViolation = PERCENT;
    297
            if (!OPTIMIZATION_MODE) {
    298 24×
                t(maxViolation <= 0, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE);
    299
            }
    300
            return maxViolation <= 0;
    301
        }
    302
    }
    100% tests/recon/Setup.sol
    Lines covered: 29 / 29 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    // Chimera deps
    5
    import {BaseSetup} from "@chimera/BaseSetup.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    8
    // Managers
    9
    import {ActorManager} from "@recon/ActorManager.sol";
    10
    import {AssetManager} from "@recon/AssetManager.sol";
    11
    12
    // Helpers
    13
    import {Utils as ReconUtils} from "@recon/Utils.sol";
    14
    15
    // Your deps
    16
    import "src/spoke/interfaces/IAaveOracle.sol";
    17
    import "src/hub/interfaces/IHub.sol";
    18
    import "src/spoke/interfaces/ISpoke.sol";
    19
    20
    import "tests/Base.t.sol";
    21
    22
    abstract contract Setup is BaseSetup, ActorManager, AssetManager, ReconUtils, Base {
    23
        mapping(address underlying => uint256 maxAmount) internal _maxByUnderlying;
    24
    25
        IAaveOracle iAaveOracle;
    26
        IHub iHub;
    27
        ISpoke iSpoke;
    28
        
    29
        /// === Setup === ///
    30
        /// This contains all calls to be performed in the tester constructor, both for Echidna and Foundry
    31
        function setup() internal virtual override {
    32
            deployFixtures();
    33
            initEnvironment();
    34
    35 15×
            iAaveOracle = oracle1;
    36 16×
            iHub = hub1;
    37 14×
            iSpoke = spoke1;
    38
    39
            _addAsset(address(tokenList.weth));
    40 98×
            _maxByUnderlying[address(tokenList.weth)] = 10e3 * 10 ** tokenList.weth.decimals();
    41
            _addAsset(address(tokenList.usdx));
    42 98×
            _maxByUnderlying[address(tokenList.usdx)] = 100e3 * 10 ** tokenList.usdx.decimals();
    43
            _addAsset(address(tokenList.dai));
    44 98×
            _maxByUnderlying[address(tokenList.dai)] = 100e3 * 10 ** tokenList.dai.decimals();
    45
            _addAsset(address(tokenList.wbtc));
    46 98×
            _maxByUnderlying[address(tokenList.wbtc)] = 10e3 * 10 ** tokenList.wbtc.decimals();
    47
            _addAsset(address(tokenList.usdy));
    48 98×
            _maxByUnderlying[address(tokenList.usdy)] = 100e3 * 10 ** tokenList.usdy.decimals();
    49
            _addAsset(address(tokenList.usdz));
    50 96×
            _maxByUnderlying[address(tokenList.usdz)] = 100e3 * 10 ** tokenList.usdz.decimals();
    51
    52
            _switchAsset(0);
    53
    54
            _addActor(alice);
    55
            _addActor(bob);
    56
            _addActor(LIQUIDATOR);
    57
    58
            _switchActor(0);
    59
        }
    60
    61
        /// === HELPERS === ///
    62
        /// Get the max amount for a given reserve based on its underlying token decimals (avoids Low likelihood scenarios in Audit Contest)
    63
        function _max(uint256 reserveId) internal view returns (uint256) {
    64 61×
            ISpoke.Reserve memory reserve = iSpoke.getReserve(reserveId);
    65
            address underlying = reserve.underlying;
    66 12×
            uint256 maxAmount = _maxByUnderlying[underlying];
    67
            require(maxAmount > 0, "Max not set for underlying");
    68
            return maxAmount;
    69
        }
    70
    }
    100% tests/recon/targets/IAaveOracleTargets.sol
    Lines covered: 17 / 17 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    import {BeforeAfter} from "../BeforeAfter.sol";
    6
    import {Properties} from "../Properties.sol";
    7
    // Chimera deps
    8
    import {vm} from "@chimera/Hevm.sol";
    9
    10
    // Helpers
    11
    import {Panic} from "@recon/Panic.sol";
    12
    13
    import "src/spoke/interfaces/IAaveOracle.sol";
    14
    import "src/dependencies/chainlink/AggregatorV3Interface.sol";
    15
    import "src/spoke/AaveOracle.sol";
    16
    import "tests/mocks/MockPriceFeed.sol";
    17
    18
    abstract contract IAaveOracleTargets is
    19
        BaseTargetFunctions,
    20
        Properties
    21
    {
    22
        /// Price can change by up to 100x or 1/100x (avoids Low likelihood scenarios in Audit Contest)
    23
        uint256 private constant MAX_PRICE_CHANGE_FACTOR = 100;
    24
        mapping(uint256 reserveId => uint256 originalPrice) private _originalPrices;
    25
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    26
    27
    28
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    29
    30
        function iAaveOracle_setReserveSource(uint256 reserveId, address source) private asActor {
    31
            iAaveOracle.setReserveSource(reserveId, source);
    32
        }
    33
    34 11×
        function iAaveOracle_setPrice(uint256 reserveId, uint256 price) public asAdmin {
    35 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    36
            require(reserveCount > 0);
    37
    38 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    39
    40
            uint256 basePrice = _getOriginalPrice(reserveId);
    41
    42 13×
            price = between(price, basePrice / MAX_PRICE_CHANGE_FACTOR, basePrice * MAX_PRICE_CHANGE_FACTOR);
    43 70×
            AaveOracle oracle = AaveOracle(iSpoke.ORACLE());
    44
            address mockPriceFeed = address(
    45 148×
                new MockPriceFeed(oracle.DECIMALS(), oracle.DESCRIPTION(), price)
    46
            );
    47 45×
            iSpoke.updateReservePriceSource(reserveId, mockPriceFeed);
    48
            _after.operation = Operation.SetPrice;
    49
        }
    50
    51 10×
        function _getOriginalPrice(uint256 reserveId) private returns (uint256 originalPrice) {
    52 11×
            originalPrice = _originalPrices[reserveId];
    53
            if (originalPrice == 0) {
    54 117×
                (, int256 answer,,,) = AggregatorV3Interface(iAaveOracle.getReserveSource(reserveId)).latestRoundData();
    55
                originalPrice = uint256(answer);
    56 13×
                _originalPrices[reserveId] = originalPrice;
    57
            }
    58
        }
    59
    }
    100% tests/recon/targets/IHubTargets.sol
    Lines covered: 32 / 32 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    import {BeforeAfter} from "../BeforeAfter.sol";
    6
    import {Properties} from "../Properties.sol";
    7
    // Chimera deps
    8
    import {vm} from "@chimera/Hevm.sol";
    9
    10
    // Helpers
    11
    import {Panic} from "@recon/Panic.sol";
    12
    13
    import "src/hub/interfaces/IHub.sol";
    14
    import "src/hub/interfaces/IAssetInterestRateStrategy.sol";
    15
    16
    abstract contract IHubTargets is BaseTargetFunctions, Properties {
    17
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    18
    19
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    20
    21
        function iHub_add(uint256 assetId, uint256 amount) private asActor {
    22
            iHub.add(assetId, amount);
    23
        }
    24
    25
        function iHub_addAsset(
    26
            address underlying,
    27
            uint8 decimals,
    28
            address feeReceiver,
    29
            address irStrategy,
    30
            bytes memory irData
    31
        ) private asActor {
    32
            iHub.addAsset(underlying, decimals, feeReceiver, irStrategy, irData);
    33
        }
    34
    35
        function iHub_addSpoke(uint256 assetId, address spoke, IHub.SpokeConfig memory params) private asActor {
    36
            iHub.addSpoke(assetId, spoke, params);
    37
        }
    38
    39
        function iHub_draw(uint256 assetId, uint256 amount, address to) private asActor {
    40
            iHub.draw(assetId, amount, to);
    41
        }
    42
    43
        function iHub_eliminateDeficit(uint256 assetId, uint256 amount, address spoke) private asActor {
    44
            iHub.eliminateDeficit(assetId, amount, spoke);
    45
        }
    46
    47 18×
        function iHub_mintFeeShares_ASSERTION_MINT_FEE_SHARES_PPS_CHANGE(uint256 assetId) public asAdmin {
    48 68×
            uint256 assetCount = iHub.getAssetCount();
    49
            require(assetCount > 0);
    50
    51 17×
            assetId = between(assetId, 0, assetCount - 1);
    52
            uint256 oldPPS = _pps(assetId);
    53 66×
            iHub.mintFeeShares(assetId);
    54
            uint256 newPPS = _pps(assetId);
    55
    56 23×
            uint256 diff = oldPPS > newPPS ? oldPPS - newPPS : newPPS - oldPPS;
    57 15×
            uint256 relativeDiff = (diff * 1e18) / oldPPS;
    58
    59 23×
            lte(relativeDiff, 1, ASSERTION_MINT_FEE_SHARES_PPS_CHANGE);
    60
        }
    61
    62
        function _pps(uint256 assetId) private view returns (uint256) {
    63 131×
            return iHub.getAddedAssets(assetId) * 1e18 / iHub.getAddedShares(assetId);
    64
        }
    65
    66
        function iHub_payFeeShares(uint256 assetId, uint256 shares) private asActor {
    67
            iHub.payFeeShares(assetId, shares);
    68
        }
    69
    70
        function iHub_reclaim(uint256 assetId, uint256 amount) private asActor {
    71
            iHub.reclaim(assetId, amount);
    72
        }
    73
    74
        function iHub_refreshPremium(uint256 assetId, IHubBase.PremiumDelta memory premiumDelta) private asActor {
    75
            iHub.refreshPremium(assetId, premiumDelta);
    76
        }
    77
    78
        function iHub_remove(uint256 assetId, uint256 amount, address to) private asActor {
    79
            iHub.remove(assetId, amount, to);
    80
        }
    81
    82
        function iHub_reportDeficit(uint256 assetId, uint256 drawnAmount, IHubBase.PremiumDelta memory premiumDelta)
    83
            private
    84
            asActor
    85
        {
    86
            iHub.reportDeficit(assetId, drawnAmount, premiumDelta);
    87
        }
    88
    89
        function iHub_restore(uint256 assetId, uint256 drawnAmount, IHubBase.PremiumDelta memory premiumDelta)
    90
            private
    91
            asActor
    92
        {
    93
            iHub.restore(assetId, drawnAmount, premiumDelta);
    94
        }
    95
    96
        function iHub_setAuthority(address) private asActor {
    97
            iHub.setAuthority(address(0));
    98
        }
    99
    100
        function iHub_setInterestRateData(uint256 assetId, bytes memory irData) private asActor {
    101
            iHub.setInterestRateData(assetId, irData);
    102
        }
    103
    104 17×
        function iHub_setInterestRateData(uint256 assetId, IAssetInterestRateStrategy.InterestRateData memory irData)
    105
            public
    106
            asAdmin
    107
        {
    108 68×
            uint256 assetCount = iHub.getAssetCount();
    109
            require(assetCount > 0);
    110
    111 13×
            assetId = between(assetId, 0, assetCount - 1);
    112 84×
            iHub.setInterestRateData(assetId, abi.encode(irData));
    113
        }
    114
    115
        function iHub_sweep(uint256 assetId, uint256 amount) private asActor {
    116
            iHub.sweep(assetId, amount);
    117
        }
    118
    119
        function iHub_transferShares(uint256 assetId, uint256 shares, address toSpoke) private asActor {
    120
            iHub.transferShares(assetId, shares, toSpoke);
    121
        }
    122
    123
        function iHub_updateAssetConfig(uint256 assetId, IHub.AssetConfig memory config, bytes memory irData)
    124
            private
    125
            asActor
    126
        {
    127
            iHub.updateAssetConfig(assetId, config, irData);
    128
        }
    129
    130 11×
        function iHub_updateAssetConfig(
    131
            uint256 assetId,
    132
            IHub.AssetConfig memory config,
    133
            IAssetInterestRateStrategy.InterestRateData memory irData
    134
        ) public asAdmin {
    135 68×
            uint256 assetCount = iHub.getAssetCount();
    136
            require(assetCount > 0);
    137
    138 13×
            assetId = between(assetId, 0, assetCount - 1);
    139
    140 49×
            iHub.updateAssetConfig(assetId, config, abi.encode(irData));
    141
        }
    142
    143
        function iHub_updateSpokeConfig(uint256 assetId, address spoke, IHub.SpokeConfig memory config) private asActor {
    144
            iHub.updateSpokeConfig(assetId, spoke, config);
    145
        }
    146
    147 18×
        function iHub_updateSpokeConfig(uint256 assetId, uint256 spokeId, IHub.SpokeConfig memory config) public asAdmin {
    148 68×
            uint256 assetCount = iHub.getAssetCount();
    149
            require(assetCount > 0);
    150
    151 11×
            assetId = between(assetId, 0, assetCount - 1);
    152
    153 63×
            uint256 spokeCount = iHub.getSpokeCount(assetId);
    154
            require(spokeCount > 0);
    155
    156 11×
            spokeId = between(spokeId, 0, spokeCount - 1);
    157
    158 63×
            address spoke = iHub.getSpokeAddress(assetId, spokeId);
    159 44×
            iHub.updateSpokeConfig(assetId, spoke, config);
    160
        }
    161
    }
    100% tests/recon/targets/ISpokeTargets.sol
    Lines covered: 117 / 117 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    import {BeforeAfter} from "../BeforeAfter.sol";
    6
    import {Properties} from "../Properties.sol";
    7
    // Chimera deps
    8
    import {vm} from "@chimera/Hevm.sol";
    9
    10
    // Helpers
    11
    import {Panic} from "@recon/Panic.sol";
    12
    13
    import "src/hub/interfaces/IHub.sol";
    14
    import "src/spoke/interfaces/ISpoke.sol";
    15
    import "src/spoke/interfaces/IAaveOracle.sol";
    16
    import "src/dependencies/openzeppelin/Ownable.sol";
    17
    import "src/dependencies/openzeppelin/IERC20Errors.sol";
    18
    import "src/dependencies/weth/WETH9.sol";
    19
    import "src/spoke/libraries/LiquidationLogic.sol";
    20
    21
    abstract contract ISpokeTargets is BaseTargetFunctions, Properties {
    22
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    23
    24
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    25
    26 11×
        function iSpoke_addDynamicReserveConfig(uint256 reserveId, ISpoke.DynamicReserveConfig memory dynamicConfig)
    27
            public
    28
            asAdmin
    29
        {
    30 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    31
            require(reserveCount > 0);
    32
    33 13×
            reserveId = between(reserveId, 0, reserveCount - 1);
    34 72×
            iSpoke.addDynamicReserveConfig(reserveId, dynamicConfig);
    35
        }
    36
    37
        function iSpoke_addReserve(
    38
            address hub,
    39
            uint256 assetId,
    40
            address priceSource,
    41
            ISpoke.ReserveConfig memory config,
    42
            ISpoke.DynamicReserveConfig memory dynamicConfig
    43
        ) private asActor {
    44
            iSpoke.addReserve(hub, assetId, priceSource, config, dynamicConfig);
    45
        }
    46
    47
        function iSpoke_borrow(uint256 reserveId, uint256 amount, address onBehalfOf) private asActor {
    48
            iSpoke.borrow(reserveId, amount, onBehalfOf);
    49
        }
    50
    51 11×
        function iSpoke_borrow(uint256 reserveId, uint256 amount) public asActor {
    52 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    53
            require(reserveCount > 0);
    54
    55 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    56 11×
            amount = between(amount, 0, _max(reserveId));
    57
    58 79×
            iSpoke.borrow(reserveId, amount, _getActor());
    59
        }
    60
    61
        function iSpoke_liquidationCall(
    62
            uint256 collateralReserveId,
    63
            uint256 debtReserveId,
    64
            address user,
    65
            uint256 debtToCover,
    66
            bool receiveShares
    67
        ) private asActor {
    68
            iSpoke.liquidationCall(collateralReserveId, debtReserveId, user, debtToCover, receiveShares);
    69
        }
    70
    71 20×
        function iSpoke_liquidationCall_ASSERTION_LIQUIDATION_CALL_DOS(uint256 collateralReserveId, uint256 debtReserveId, uint256 userId, uint256 debtToCover, bool receiveShares) public asActor {
    72 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    73
            require(reserveCount > 1);
    74
            address[] memory actors = _getActors();
    75
            require(actors.length > 0);
    76
    77 10×
            collateralReserveId = between(collateralReserveId, 0, reserveCount - 1);
    78 11×
            debtReserveId = between(debtReserveId, 0, reserveCount - 1);
    79 10×
            debtToCover = between(debtToCover, 0, _max(debtReserveId));
    80 14×
            userId = between(userId, 0, actors.length - 1);
    81 22×
            address user = actors[userId];
    82
    83 84×
            try iSpoke.liquidationCall(collateralReserveId, debtReserveId, user, debtToCover, receiveShares) {}
    84
            catch (bytes memory err) {
    85
                t(
    86 89×
                    bytes4(err) == ISpoke.ReserveNotListed.selector || bytes4(err) == ISpoke.SelfLiquidation.selector
    87
                        || bytes4(err) == ISpoke.InvalidDebtToCover.selector
    88
                        || bytes4(err) == IERC20Errors.ERC20InsufficientAllowance.selector
    89
                        || bytes4(err) == IERC20Errors.ERC20InsufficientBalance.selector
    90
                        || bytes4(err) == WETH9.InsufficientAllowance.selector
    91 14×
                        || bytes4(err) == WETH9.InsufficientBalance.selector || bytes4(err) == ISpoke.ReservePaused.selector
    92
                        || bytes4(err) == ISpoke.ReserveNotSupplied.selector
    93
                        || bytes4(err) == ISpoke.ReserveNotBorrowed.selector
    94
                        || bytes4(err) == ISpoke.CollateralCannotBeLiquidated.selector
    95
                        || bytes4(err) == ISpoke.HealthFactorNotBelowThreshold.selector
    96
                        || bytes4(err) == ISpoke.ReserveNotEnabledAsCollateral.selector
    97 14×
                        || bytes4(err) == ISpoke.CannotReceiveShares.selector || bytes4(err) == ISpoke.MustNotLeaveDust.selector
    98 14×
                        || bytes4(err) == IHub.InvalidAmount.selector || bytes4(err) == IHub.SpokeNotActive.selector
    99
                        || bytes4(err) == IHub.SpokePaused.selector
    100 12×
                        || (bytes4(err) == IHub.InsufficientLiquidity.selector && !receiveShares)
    101
                        || bytes4(err) == IAaveOracle.InvalidPrice.selector,
    102 17×
                    ASSERTION_LIQUIDATION_CALL_DOS
    103
                );
    104
                require(false);
    105
            }
    106
        }
    107
    108
        function iSpoke_multicall(bytes[] memory data) private asActor {
    109
            iSpoke.multicall(data);
    110
        }
    111
    112
        function iSpoke_permitReserve(
    113
            uint256 reserveId,
    114
            address onBehalfOf,
    115
            uint256 value,
    116
            uint256 deadline,
    117
            uint8 permitV,
    118
            bytes32 permitR,
    119
            bytes32 permitS
    120
        ) private asActor {
    121
            iSpoke.permitReserve(reserveId, onBehalfOf, value, deadline, permitV, permitR, permitS);
    122
        }
    123
    124
        function iSpoke_renouncePositionManagerRole(address user) private asActor {
    125
            iSpoke.renouncePositionManagerRole(user);
    126
        }
    127
    128
        function iSpoke_repay(uint256 reserveId, uint256 amount, address onBehalfOf) private asActor {
    129
            iSpoke.repay(reserveId, amount, onBehalfOf);
    130
        }
    131
    132 11×
        function iSpoke_repay_ASSERTION_REPAY_DOS(uint256 reserveId, uint256 amount) public asActor {
    133 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    134
            require(reserveCount > 1);
    135
    136 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    137 11×
            amount = between(amount, 0, _max(reserveId));
    138
    139
            uint256 value0;
    140
            uint256 value1;
    141 105×
            try iSpoke.repay(reserveId, amount, _getActor()) returns (uint256 tempValue0, uint256 tempValue1) {
    142
                value0 = tempValue0;
    143
                value1 = tempValue1;
    144
            } catch (bytes memory err) {
    145
                t(
    146 62×
                    bytes4(err) == ISpoke.Unauthorized.selector || bytes4(err) == ISpoke.ReserveNotListed.selector
    147
                        || bytes4(err) == ISpoke.ReservePaused.selector
    148
                        || bytes4(err) == IERC20Errors.ERC20InsufficientAllowance.selector
    149
                        || bytes4(err) == IERC20Errors.ERC20InsufficientBalance.selector
    150
                        || bytes4(err) == WETH9.InsufficientAllowance.selector
    151 14×
                        || bytes4(err) == WETH9.InsufficientBalance.selector || bytes4(err) == IHub.InvalidAmount.selector
    152 14×
                        || bytes4(err) == IHub.SpokeNotActive.selector || bytes4(err) == IHub.SpokePaused.selector
    153
                        || bytes4(err) == IHub.SurplusDrawnRestored.selector
    154
                        || bytes4(err) == IHub.SurplusPremiumRayRestored.selector
    155
                        || bytes4(err) == IAaveOracle.InvalidPrice.selector,
    156 16×
                    ASSERTION_REPAY_DOS
    157
                );
    158
                require(false);
    159
            }
    160
        }
    161
    162
        function iSpoke_setAuthority(address) private asActor {
    163
            iSpoke.setAuthority(address(0));
    164
        }
    165
    166
        function iSpoke_setUserPositionManager(address positionManager, bool approve) private asActor {
    167
            iSpoke.setUserPositionManager(positionManager, approve);
    168
        }
    169
    170
        function iSpoke_setUserPositionManagerWithSig(
    171
            address positionManager,
    172
            address user,
    173
            bool approve,
    174
            uint256 nonce,
    175
            uint256 deadline,
    176
            bytes memory signature
    177
        ) private asActor {
    178
            iSpoke.setUserPositionManagerWithSig(positionManager, user, approve, nonce, deadline, signature);
    179
        }
    180
    181
        function iSpoke_setUsingAsCollateral(uint256 reserveId, bool usingAsCollateral, address onBehalfOf)
    182
            private
    183
            asActor
    184
        {
    185
            iSpoke.setUsingAsCollateral(reserveId, usingAsCollateral, onBehalfOf);
    186
        }
    187
    188 11×
        function iSpoke_setUsingAsCollateral(uint256 reserveId, bool usingAsCollateral) public asActor {
    189 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    190
            require(reserveCount > 1);
    191
    192 12×
            reserveId = between(reserveId, 0, reserveCount - 1);
    193 22×
            iSpoke.setUsingAsCollateral(reserveId, usingAsCollateral, _getActor());
    194
        }
    195
    196
        function iSpoke_supply(uint256 reserveId, uint256 amount, address onBehalfOf) private asActor {
    197
            iSpoke.supply(reserveId, amount, onBehalfOf);
    198
        }
    199
    200 14×
        function iSpoke_supply_ASSERTION_SUPPLY_DOS(uint256 reserveId, uint256 amount) public asActor {
    201 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    202
            require(reserveCount > 1);
    203
    204 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    205 11×
            amount = between(amount, 0, _max(reserveId));
    206
    207
            uint256 value0;
    208
            uint256 value1;
    209 107×
            try iSpoke.supply(reserveId, amount, _getActor()) returns (uint256 tempValue0, uint256 tempValue1) {
    210
                value0 = tempValue0;
    211
                value1 = tempValue1;
    212
            } catch (bytes memory err) {
    213
                t(
    214 58×
                    bytes4(err) == ISpoke.ReserveNotListed.selector || bytes4(err) == ISpoke.ReservePaused.selector
    215
                        || bytes4(err) == IERC20Errors.ERC20InsufficientAllowance.selector
    216
                        || bytes4(err) == IERC20Errors.ERC20InsufficientBalance.selector
    217
                        || bytes4(err) == WETH9.InsufficientAllowance.selector
    218 14×
                        || bytes4(err) == WETH9.InsufficientBalance.selector || bytes4(err) == ISpoke.ReserveFrozen.selector
    219 14×
                        || bytes4(err) == IHub.InvalidShares.selector || bytes4(err) == IHub.InvalidAmount.selector
    220 14×
                        || bytes4(err) == IHub.SpokeNotActive.selector || bytes4(err) == IHub.AddCapExceeded.selector
    221
                        || bytes4(err) == IHub.SpokePaused.selector,
    222 16×
                    ASSERTION_SUPPLY_DOS
    223
                );
    224
                require(false);
    225
            }
    226
        }
    227
    228 11×
        function iSpoke_updateDynamicReserveConfig(
    229
            uint256 reserveId,
    230
            uint24 dynamicConfigKey,
    231
            ISpoke.DynamicReserveConfig memory dynamicConfig
    232
        ) public asAdmin {
    233 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    234
            require(reserveCount > 0);
    235
    236 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    237
    238 79×
            dynamicConfigKey = uint24(between(dynamicConfigKey, 0, uint256(iSpoke.getReserve(reserveId).dynamicConfigKey)));
    239 58×
            iSpoke.updateDynamicReserveConfig(reserveId, dynamicConfigKey, dynamicConfig);
    240
        }
    241
    242 11×
        function iSpoke_updateLiquidationConfig(ISpoke.LiquidationConfig memory config) public asAdmin {
    243 53×
            iSpoke.updateLiquidationConfig(config);
    244
        }
    245
    246
        function iSpoke_updatePositionManager(address positionManager, bool active) private asActor {
    247
            iSpoke.updatePositionManager(positionManager, active);
    248
        }
    249
    250 11×
        function iSpoke_updateReserveConfig(uint256 reserveId, ISpoke.ReserveConfig memory params) public asAdmin {
    251 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    252
            require(reserveCount > 0);
    253
    254 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    255 15×
            iSpoke.updateReserveConfig(reserveId, params);
    256
        }
    257
    258
        function iSpoke_updateReservePriceSource(uint256 reserveId, address priceSource) private asActor {
    259
            iSpoke.updateReservePriceSource(reserveId, priceSource);
    260
        }
    261
    262
        function iSpoke_updateUserDynamicConfig(address onBehalfOf) private asActor {
    263
            iSpoke.updateUserDynamicConfig(onBehalfOf);
    264
        }
    265
    266
        function iSpoke_updateUserDynamicConfig() public asActor {
    267
            iSpoke.updateUserDynamicConfig(_getActor());
    268
        }
    269
    270
        function iSpoke_updateUserRiskPremium(address onBehalfOf) private asActor {
    271
            iSpoke.updateUserRiskPremium(onBehalfOf);
    272
        }
    273
    274
        function iSpoke_updateUserRiskPremium() public asActor {
    275 62×
            iSpoke.updateUserRiskPremium(_getActor());
    276
        }
    277
    278
        function iSpoke_useNonce(uint192 key) private asActor {
    279
            iSpoke.useNonce(key);
    280
        }
    281
    282
        function iSpoke_withdraw(uint256 reserveId, uint256 amount, address onBehalfOf) private asActor {
    283
            iSpoke.withdraw(reserveId, amount, onBehalfOf);
    284
        }
    285
    286 11×
        function iSpoke_withdraw_ASSERTION_WITHDRAW_DOS(uint256 reserveId, uint256 amount) public asActor {
    287 68×
            uint256 reserveCount = iSpoke.getReserveCount();
    288
            require(reserveCount > 1);
    289
    290 11×
            reserveId = between(reserveId, 0, reserveCount - 1);
    291 11×
            amount = between(amount, 0, _max(reserveId));
    292
    293
            uint256 value0;
    294
            uint256 value1;
    295 105×
            try iSpoke.withdraw(reserveId, amount, _getActor()) returns (uint256 tempValue0, uint256 tempValue1) {
    296
                value0 = tempValue0;
    297
                value1 = tempValue1;
    298
            } catch (bytes memory err) {
    299
                t(
    300 50×
                    bytes4(err) == ISpoke.Unauthorized.selector || bytes4(err) == ISpoke.ReserveNotListed.selector
    301 14×
                        || bytes4(err) == ISpoke.ReservePaused.selector || bytes4(err) == IHub.InvalidAddress.selector
    302 14×
                        || bytes4(err) == IHub.InvalidAmount.selector || bytes4(err) == IHub.SpokeNotActive.selector
    303 14×
                        || bytes4(err) == IHub.SpokePaused.selector || bytes4(err) == IHub.InsufficientLiquidity.selector
    304
                        || bytes4(err) == ISpoke.HealthFactorBelowThreshold.selector
    305
                        || bytes4(err) == Ownable.OwnableUnauthorizedAccount.selector,
    306 16×
                    ASSERTION_WITHDRAW_DOS
    307
                );
    308
                require(false);
    309
            }
    310
        }
    311
    }
    100% tests/recon/targets/ManagersTargets.sol
    Lines covered: 16 / 16 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    import {BeforeAfter} from "../BeforeAfter.sol";
    6
    import {Properties} from "../Properties.sol";
    7
    import {vm} from "@chimera/Hevm.sol";
    8
    9
    import {MockERC20} from "@recon/MockERC20.sol";
    10
    11
    12
    // Target functions that are effectively inherited from the Actor and AssetManagers
    13
    // Once properly standardized, managers will expose these by default
    14
    // Keeping them out makes your project more custom
    15
    abstract contract ManagersTargets is
    16
        BaseTargetFunctions,
    17
        Properties
    18
    {
    19
        // == ACTOR HANDLERS == //
    20
        
    21
        /// @dev Start acting as another actor
    22
        /// @dev Skip address(this) as it is not seeded by Setup
    23
        /// @dev Separate ADMIN from regular users to discard 'admin is trusted' scenarios
    24 17×
        function switchActor(uint256 entropy) public {
    25
            uint256 actorCount = _getActors().length;
    26
            require(actorCount > 1);
    27 11×
            entropy = between(entropy, 1, actorCount - 1);
    28
            _switchActor(entropy);
    29
        }
    30
    31
    32
        /// @dev Starts using a new asset
    33 17×
        function switch_asset(uint256 entropy) public {
    34
            uint256 assetCount = _getAssets().length;
    35
            require(assetCount > 0);
    36 11×
            entropy = between(entropy, 0, assetCount - 1);
    37
            _switchAsset(entropy);
    38
        }
    39
    40 12×
        function switch_spoke(uint256 entropy) public {
    41
            uint256 index = between(entropy, 0, 2);
    42 39×
            iSpoke = index == 0 ? spoke1 : index == 1 ? spoke2 : spoke3;
    43
        }
    44
    45 12×
        function switch_oracle(uint256 entropy) public {
    46
            uint256 index = between(entropy, 0, 2);
    47 39×
            iAaveOracle = index == 0 ? oracle1 : index == 1 ? oracle2 : oracle3;
    48
        }
    49
    50
        /// @dev Deploy a new token and add it to the list of assets, then set it as the current asset
    51
        function add_new_asset(uint8 decimals) private returns (address) {
    52
            address newAsset = _newAsset(decimals);
    53
            return newAsset;
    54
        }
    55
    56
        /// === GHOST UPDATING HANDLERS ===///
    57
        /// We `updateGhosts` cause you never know (e.g. donations)
    58
        /// If you don't want to track donations, remove the `updateGhosts`
    59
    60
        /// @dev Approve to arbitrary address, uses Actor by default
    61
        /// NOTE: You're almost always better off setting approvals in `Setup`
    62
        function asset_approve(address to, uint128 amt) private asActor {
    63
            MockERC20(_getAsset()).approve(to, amt);
    64
        }
    65
    66
        /// @dev Mint to arbitrary address, uses owner by default, even though MockERC20 doesn't check
    67
        function asset_mint(address to, uint128 amt) private asAdmin {
    68
            MockERC20(_getAsset()).mint(to, amt);
    69
        }
    70
    }