Recon Coverage Report 57% coverage
Files 92 files
    Files
    92
    Lines
    23530
    Coverage
    57%
    4122 / 7230
    Actions
    92% lib/erc7540-reusable-properties/src/ERC7540Properties.sol
    Lines covered: 114 / 123 (92%)
    1
    // SPDX-License-Identifier: AGPL-3.0-only
    2
    pragma solidity ^0.8.21;
    3
    4
    import {Asserts} from "@chimera/Asserts.sol";
    5
    6
    interface IShareLike {
    7
        function balanceOf(address) external view returns (uint256);
    8
        function totalSupply() external view returns (uint256);
    9
        function decimals() external view returns (uint8);
    10
    }
    11
    12
    interface IERC7540Like {
    13
        function share() external view returns (address shareTokenAddress);
    14
        function convertToShares(
    15
            uint256 assets
    16
        ) external view returns (uint256 shares);
    17
        function convertToAssets(
    18
            uint256 shares
    19
        ) external view returns (uint256 assets);
    20
        function totalAssets() external view returns (uint256 totalManagedAssets);
    21
        function maxDeposit(
    22
            address receiver
    23
        ) external view returns (uint256 maxAssets);
    24
        function previewDeposit(
    25
            uint256 assets
    26
        ) external view returns (uint256 shares);
    27
        function deposit(
    28
            uint256 assets,
    29
            address receiver
    30
        ) external returns (uint256 shares);
    31
        function maxMint(
    32
            address receiver
    33
        ) external view returns (uint256 maxShares);
    34
        function previewMint(uint256 shares) external view returns (uint256 assets);
    35
        function mint(
    36
            uint256 shares,
    37
            address receiver
    38
        ) external returns (uint256 assets);
    39
        function maxWithdraw(
    40
            address owner
    41
        ) external view returns (uint256 maxAssets);
    42
        function previewWithdraw(
    43
            uint256 assets
    44
        ) external view returns (uint256 shares);
    45
        function withdraw(
    46
            uint256 assets,
    47
            address receiver,
    48
            address owner
    49
        ) external returns (uint256 shares);
    50
        function maxRedeem(address owner) external view returns (uint256 maxShares);
    51
        function previewRedeem(
    52
            uint256 shares
    53
        ) external view returns (uint256 assets);
    54
        function redeem(
    55
            uint256 shares,
    56
            address receiver,
    57
            address owner
    58
        ) external returns (uint256 assets);
    59
    60
        function requestRedeem(
    61
            uint256 shares,
    62
            address receiver,
    63
            address owner,
    64
            bytes calldata data
    65
        ) external returns (uint256 requestId);
    66
    }
    67
    68
    /// @dev ERC-7540 Properties
    69
    /// @author The Recon Team
    70
    /// @notice A set of reuseable tests for your ERC7540 Vaults
    71
    ///     To get started, extend from this contract and make sure to add a way to set the actor (the current user)
    72
    ///     For more info: https://getrecon.xyz/
    73
    abstract contract ERC7540Properties is Asserts {
    74 16×
        uint256 public constant MAX_ROUNDING_ERROR = 10 ** 18;
    75
    76
        address actor;
    77
    78
        /// @audit TODO: You must add a way to change this!
    79
    80
        /// @dev 7540-1	convertToAssets(totalSupply) == totalAssets unless price is 0.0
    81 25×
        function erc7540_1(address erc7540Target) public virtual returns (bool) {
    82
            // Doesn't hold on zero price
    83
            if (
    84 62×
                IERC7540Like(erc7540Target).convertToAssets(
    85 128×
                    10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals()
    86
                ) == 0
    87
            ) return true;
    88
    89
            return
    90 63×
                IERC7540Like(erc7540Target).convertToAssets(
    91 115×
                    IShareLike(IERC7540Like(erc7540Target).share()).totalSupply()
    92 58×
                ) == IERC7540Like(erc7540Target).totalAssets();
    93
        }
    94
    95
        /// @dev 7540-2	convertToShares(totalAssets) == totalSupply unless price is 0.0
    96 25×
        function erc7540_2(address erc7540Target) public virtual returns (bool) {
    97
            if (
    98 62×
                IERC7540Like(erc7540Target).convertToAssets(
    99 128×
                    10 ** IShareLike(IERC7540Like(erc7540Target).share()).decimals()
    100
                ) == 0
    101
            ) return true;
    102
    103
            // convertToShares(totalAssets) == totalSupply
    104
            return
    105
                _diff(
    106 61×
                    IERC7540Like(erc7540Target).convertToShares(
    107 58×
                        IERC7540Like(erc7540Target).totalAssets()
    108
                    ),
    109 115×
                    IShareLike(IERC7540Like(erc7540Target).share()).totalSupply()
    110
                ) <= MAX_ROUNDING_ERROR;
    111
        }
    112
    113
        function _diff(uint256 a, uint256 b) internal pure returns (uint256) {
    114 19×
            return a > b ? a - b : b - a;
    115
        }
    116
    117
        /// @dev 7540-3	max* never reverts
    118 26×
        function erc7540_3(address erc7540Target) public virtual returns (bool) {
    119
            // max* never reverts
    120 65×
            try IERC7540Like(erc7540Target).maxDeposit(actor) {} catch {
    121
                return false;
    122
            }
    123 66×
            try IERC7540Like(erc7540Target).maxMint(actor) {} catch {
    124 0
                return false;
    125
            }
    126 66×
            try IERC7540Like(erc7540Target).maxRedeem(actor) {} catch {
    127
                return false;
    128
            }
    129 67×
            try IERC7540Like(erc7540Target).maxWithdraw(actor) {} catch {
    130 0
                return false;
    131
            }
    132
    133
            return true;
    134
        }
    135
    136
        /// == erc7540_4 == //
    137
    138
        /// @dev 7540-4 claiming more than max always reverts
    139 21×
        function erc7540_4_deposit(
    140
            address erc7540Target,
    141
            uint256 amt
    142
        ) public virtual returns (bool) {
    143
            // Skip 0
    144 12×
            if (amt == 0) {
    145
                return true; // Skip
    146
            }
    147
    148 139×
            uint256 maxDep = IERC7540Like(erc7540Target).maxDeposit(actor);
    149
    150
            /// @audit No Revert is proven by erc7540_5
    151
    152 16×
            uint256 sum = maxDep + amt;
    153 12×
            if (sum == 0) {
    154 0
                return true; // Needs to be greater than 0, skip
    155
            }
    156
    157 16×
            try IERC7540Like(erc7540Target).deposit(maxDep + amt, actor) {
    158
                return false;
    159
            } catch {
    160
                // We want this to be hit
    161
                return true; // So we explicitly return here, as a means to ensure that this is the code path
    162
            }
    163
    164
            // NOTE: This code path is never hit per the above
    165
        }
    166
    167 21×
        function erc7540_4_mint(
    168
            address erc7540Target,
    169
            uint256 amt
    170
        ) public virtual returns (bool) {
    171
            // Skip 0
    172 12×
            if (amt == 0) {
    173
                return true;
    174
            }
    175
    176 139×
            uint256 maxDep = IERC7540Like(erc7540Target).maxMint(actor);
    177
    178 16×
            uint256 sum = maxDep + amt;
    179 12×
            if (sum == 0) {
    180 0
                return true; // Needs to be greater than 0, skip
    181
            }
    182
    183 64×
            try IERC7540Like(erc7540Target).mint(maxDep + amt, actor) {
    184
                return false;
    185
            } catch {
    186
                // We want this to be hit
    187
                return true; // So we explicitly return here, as a means to ensure that this is the code path
    188
            }
    189
    190
            // NOTE: This code path is never hit per the above
    191
        }
    192
    193 21×
        function erc7540_4_withdraw(
    194
            address erc7540Target,
    195
            uint256 amt
    196
        ) public virtual returns (bool) {
    197
            // Skip 0
    198 12×
            if (amt == 0) {
    199
                return true;
    200
            }
    201
    202 139×
            uint256 maxDep = IERC7540Like(erc7540Target).maxWithdraw(actor);
    203
    204 16×
            uint256 sum = maxDep + amt;
    205 12×
            if (sum == 0) {
    206 0
                return true; // Needs to be greater than 0
    207
            }
    208
    209 16×
            try IERC7540Like(erc7540Target).withdraw(maxDep + amt, actor, actor) {
    210
                return false;
    211
            } catch {
    212
                // We want this to be hit
    213
                return true; // So we explicitly return here, as a means to ensure that this is the code path
    214
            }
    215
    216
            // NOTE: This code path is never hit per the above
    217
        }
    218
    219 33×
        function erc7540_4_redeem(
    220
            address erc7540Target,
    221
            uint256 amt
    222
        ) public virtual returns (bool) {
    223
            // Skip 0
    224 12×
            if (amt == 0) {
    225
                return true;
    226
            }
    227
    228 139×
            uint256 maxDep = IERC7540Like(erc7540Target).maxRedeem(actor);
    229
    230 16×
            uint256 sum = maxDep + amt;
    231 12×
            if (sum == 0) {
    232 0
                return true; // Needs to be greater than 0
    233
            }
    234
    235 139×
            try IERC7540Like(erc7540Target).redeem(maxDep + amt, actor, actor) {
    236
                return false;
    237
            } catch {
    238
                // We want this to be hit
    239 14×
                return true; // So we explicitly return here, as a means to ensure that this is the code path
    240
            }
    241
    242
            // NOTE: This code path is never hit per the above
    243
        }
    244
    245
        /// == END erc7540_4 == //
    246
    247
        /// @dev 7540-5	requestRedeem reverts if the share balance is less than amount
    248 23×
        function erc7540_5(
    249
            address erc7540Target,
    250
            address shareToken,
    251
            uint256 shares
    252
        ) public virtual returns (bool) {
    253 12×
            if (shares == 0) {
    254
                return true; // Skip
    255
            }
    256
    257 139×
            uint256 actualBal = IShareLike(shareToken).balanceOf(actor);
    258 16×
            uint256 balWeWillUse = actualBal + shares;
    259
    260 12×
            if (balWeWillUse == 0) {
    261 0
                return true; // Skip
    262
            }
    263
    264
            try
    265 87×
                IERC7540Like(erc7540Target).requestRedeem(
    266
                    balWeWillUse,
    267 18×
                    actor,
    268
                    actor,
    269
                    ""
    270
                )
    271
            {
    272
                return false;
    273
            } catch {
    274 14×
                return true;
    275
            }
    276
    277
            // NOTE: This code path is never hit per the above
    278
        }
    279
    280
        /// @dev 7540-6	preview* always reverts
    281 28×
        function erc7540_6(address erc7540Target) public virtual returns (bool) {
    282
            // preview* always reverts
    283 58×
            try IERC7540Like(erc7540Target).previewDeposit(0) {
    284
                return false;
    285
            } catch {}
    286 59×
            try IERC7540Like(erc7540Target).previewMint(0) {
    287
                return false;
    288
            } catch {}
    289 36×
            try IERC7540Like(erc7540Target).previewRedeem(0) {
    290 0
                return false;
    291
            } catch {}
    292 36×
            try IERC7540Like(erc7540Target).previewWithdraw(0) {
    293 0
                return false;
    294
            } catch {}
    295
    296
            return true;
    297
        }
    298
    299
        /// == erc7540_7 == //
    300
    301
        /// @dev 7540-7 if max[method] > 0, then [method] (max) should not revert
    302 20×
        function erc7540_7_deposit(
    303
            address erc7540Target,
    304
            uint256 amt
    305
        ) public virtual returns (bool) {
    306
            // Per erc7540_4
    307 74×
            uint256 maxDeposit = IERC7540Like(erc7540Target).maxDeposit(actor);
    308
            amt = between(amt, 0, maxDeposit);
    309
    310
            if (amt == 0) {
    311
                return true; // Skip
    312
            }
    313
    314 25×
            try IERC7540Like(erc7540Target).deposit(amt, actor) {
    315
                // Success here
    316
                return true;
    317
            } catch {
    318
                return false;
    319
            }
    320
    321
            // NOTE: This code path is never hit per the above
    322
        }
    323
    324 20×
        function erc7540_7_mint(
    325
            address erc7540Target,
    326
            uint256 amt
    327
        ) public virtual returns (bool) {
    328 74×
            uint256 maxMint = IERC7540Like(erc7540Target).maxMint(actor);
    329
            amt = between(amt, 0, maxMint);
    330
    331
            if (amt == 0) {
    332
                return true; // Skip
    333
            }
    334
    335 25×
            try IERC7540Like(erc7540Target).mint(amt, actor) {
    336
                // Success here
    337
                return true;
    338
            } catch {
    339
                return false;
    340
            }
    341
    342
            // NOTE: This code path is never hit per the above
    343
        }
    344
    345 31×
        function erc7540_7_withdraw(
    346
            address erc7540Target,
    347
            uint256 amt
    348 10×
        ) public virtual returns (bool) {
    349 141×
            uint256 maxWithdraw = IERC7540Like(erc7540Target).maxWithdraw(actor);
    350 18×
            amt = between(amt, 0, maxWithdraw);
    351
    352 11×
            if (amt == 0) {
    353 12×
                return true; // Skip
    354
            }
    355
    356 77×
            try IERC7540Like(erc7540Target).withdraw(amt, actor, actor) {
    357
                // Success here
    358
                return true;
    359
            } catch {
    360
                return false;
    361
            }
    362
    363
            // NOTE: This code path is never hit per the above
    364
        }
    365
    366 20×
        function erc7540_7_redeem(
    367
            address erc7540Target,
    368
            uint256 amt
    369
        ) public virtual returns (bool) {
    370
            // Per erc7540_4
    371 74×
            uint256 maxRedeem = IERC7540Like(erc7540Target).maxRedeem(actor);
    372
            amt = between(amt, 0, maxRedeem);
    373
    374
            if (amt == 0) {
    375
                return true; // Skip
    376
            }
    377
    378 27×
            try IERC7540Like(erc7540Target).redeem(amt, actor, actor) {
    379
                return true;
    380
            } catch {
    381
                return false;
    382
            }
    383
    384
            // NOTE: This code path is never hit per the above
    385
        }
    386
    387
        /// == END erc7540_7 == //
    388
    }
    90% lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol
    Lines covered: 27 / 30 (90%)
    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 = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
    78
    79
        /**
    80
         * @dev The contract is already initialized.
    81
         */
    82
        error InvalidInitialization();
    83
    84
        /**
    85
         * @dev The contract is not initializing.
    86
         */
    87
        error NotInitializing();
    88
    89
        /**
    90
         * @dev Triggered when the contract has been initialized or reinitialized.
    91
         */
    92
        event Initialized(uint64 version);
    93
    94
        /**
    95
         * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
    96
         * `onlyInitializing` functions can be used to initialize parent contracts.
    97
         *
    98
         * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
    99
         * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
    100
         * production.
    101
         *
    102
         * Emits an {Initialized} event.
    103
         */
    104 10×
        modifier initializer() {
    105
            // solhint-disable-next-line var-name-mixedcase
    106 14×
            InitializableStorage storage $ = _getInitializableStorage();
    107
    108
            // Cache values to avoid duplicated sloads
    109 18×
            bool isTopLevelCall = !$._initializing;
    110
            uint64 initialized = $._initialized;
    111
    112
            // Allowed calls:
    113
            // - initialSetup: the contract is not in the initializing state and no previous version was
    114
            //                 initialized
    115
            // - construction: the contract is initialized at version 1 (no reinitialization) and the
    116
            //                 current contract is just being deployed
    117 22×
            bool initialSetup = initialized == 0 && isTopLevelCall;
    118 25×
            bool construction = initialized == 1 && address(this).code.length == 0;
    119
    120 22×
            if (!initialSetup && !construction) {
    121 0
                revert InvalidInitialization();
    122
            }
    123 14×
            $._initialized = 1;
    124 10×
            if (isTopLevelCall) {
    125 12×
                $._initializing = true;
    126
            }
    127
            _;
    128 10×
            if (isTopLevelCall) {
    129 10×
                $._initializing = false;
    130 22×
                emit Initialized(1);
    131
            }
    132
        }
    133
    134
        /**
    135
         * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
    136
         * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
    137
         * used to initialize parent contracts.
    138
         *
    139
         * A reinitializer may be used after the original initialization step. This is essential to configure modules that
    140
         * are added through upgrades and that require initialization.
    141
         *
    142
         * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
    143
         * cannot be nested. If one is invoked in the context of another, execution will revert.
    144
         *
    145
         * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
    146
         * a contract, executing them in the right order is up to the developer or operator.
    147
         *
    148
         * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
    149
         *
    150
         * Emits an {Initialized} event.
    151
         */
    152
        modifier reinitializer(uint64 version) {
    153
            // solhint-disable-next-line var-name-mixedcase
    154
            InitializableStorage storage $ = _getInitializableStorage();
    155
    156
            if ($._initializing || $._initialized >= version) {
    157
                revert InvalidInitialization();
    158
            }
    159
            $._initialized = version;
    160
            $._initializing = true;
    161
            _;
    162
            $._initializing = false;
    163
            emit Initialized(version);
    164
        }
    165
    166
        /**
    167
         * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
    168
         * {initializer} and {reinitializer} modifiers, directly or indirectly.
    169
         */
    170
        modifier onlyInitializing() {
    171 30×
            _checkInitializing();
    172
            _;
    173
        }
    174
    175
        /**
    176
         * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
    177
         */
    178
        function _checkInitializing() internal view virtual {
    179 12×
            if (!_isInitializing()) {
    180 0
                revert NotInitializing();
    181
            }
    182
        }
    183
    184
        /**
    185
         * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
    186
         * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
    187
         * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
    188
         * through proxies.
    189
         *
    190
         * Emits an {Initialized} event the first time it is successfully executed.
    191
         */
    192
        function _disableInitializers() internal virtual {
    193
            // solhint-disable-next-line var-name-mixedcase
    194
            InitializableStorage storage $ = _getInitializableStorage();
    195
    196 22×
            if ($._initializing) {
    197 0
                revert InvalidInitialization();
    198
            }
    199 18×
            if ($._initialized != type(uint64).max) {
    200 16×
                $._initialized = type(uint64).max;
    201 22×
                emit Initialized(type(uint64).max);
    202
            }
    203
        }
    204
    205
        /**
    206
         * @dev Returns the highest version that has been initialized. See {reinitializer}.
    207
         */
    208
        function _getInitializedVersion() internal view returns (uint64) {
    209
            return _getInitializableStorage()._initialized;
    210
        }
    211
    212
        /**
    213
         * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
    214
         */
    215
        function _isInitializing() internal view returns (bool) {
    216 22×
            return _getInitializableStorage()._initializing;
    217
        }
    218
    219
        /**
    220
         * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
    221
         *
    222
         * NOTE: Consider following the ERC-7201 formula to derive storage locations.
    223
         */
    224
        function _initializableStorageSlot() internal pure virtual returns (bytes32) {
    225
            return INITIALIZABLE_STORAGE;
    226
        }
    227
    228
        /**
    229
         * @dev Returns a pointer to the storage namespace.
    230
         */
    231
        // solhint-disable-next-line var-name-mixedcase
    232
        function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
    233
            bytes32 slot = _initializableStorageSlot();
    234
            assembly {
    235
                $.slot := slot
    236
            }
    237
        }
    238
    }
    85% lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol
    Lines covered: 57 / 67 (85%)
    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 "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    7
    import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
    8
    import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
    9
    import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
    10
    import {Initializable} from "../../proxy/utils/Initializable.sol";
    11
    12
    /**
    13
     * @dev Implementation of the {IERC20} interface.
    14
     *
    15
     * This implementation is agnostic to the way tokens are created. This means
    16
     * that a supply mechanism has to be added in a derived contract using {_mint}.
    17
     *
    18
     * TIP: For a detailed writeup see our guide
    19
     * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
    20
     * to implement supply mechanisms].
    21
     *
    22
     * The default value of {decimals} is 18. To change this, you should override
    23
     * this function so it returns a different value.
    24
     *
    25
     * We have followed general OpenZeppelin Contracts guidelines: functions revert
    26
     * instead returning `false` on failure. This behavior is nonetheless
    27
     * conventional and does not conflict with the expectations of ERC-20
    28
     * applications.
    29
     */
    30
    abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
    31
        /// @custom:storage-location erc7201:openzeppelin.storage.ERC20
    32
        struct ERC20Storage {
    33
            mapping(address account => uint256) _balances;
    34
    35
            mapping(address account => mapping(address spender => uint256)) _allowances;
    36
    37
            uint256 _totalSupply;
    38
    39
            string _name;
    40
            string _symbol;
    41
        }
    42
    43
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
    44
        bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;
    45
    46
        function _getERC20Storage() private pure returns (ERC20Storage storage $) {
    47
            assembly {
    48 0
                $.slot := ERC20StorageLocation
    49
            }
    50
        }
    51
    52
        /**
    53
         * @dev Sets the values for {name} and {symbol}.
    54
         *
    55
         * Both values are immutable: they can only be set once during construction.
    56
         */
    57 10×
        function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
    58
            __ERC20_init_unchained(name_, symbol_);
    59
        }
    60
    61
        function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
    62
            ERC20Storage storage $ = _getERC20Storage();
    63
            $._name = name_;
    64
            $._symbol = symbol_;
    65
        }
    66
    67
        /**
    68
         * @dev Returns the name of the token.
    69
         */
    70 0
        function name() public view virtual returns (string memory) {
    71 0
            ERC20Storage storage $ = _getERC20Storage();
    72 0
            return $._name;
    73
        }
    74
    75
        /**
    76
         * @dev Returns the symbol of the token, usually a shorter version of the
    77
         * name.
    78
         */
    79 0
        function symbol() public view virtual returns (string memory) {
    80
            ERC20Storage storage $ = _getERC20Storage();
    81 0
            return $._symbol;
    82
        }
    83
    84
        /**
    85
         * @dev Returns the number of decimals used to get its user representation.
    86
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
    87
         * be displayed to a user as `5.05` (`505 / 10 ** 2`).
    88
         *
    89
         * Tokens usually opt for a value of 18, imitating the relationship between
    90
         * Ether and Wei. This is the default value returned by this function, unless
    91
         * it's overridden.
    92
         *
    93
         * NOTE: This information is only used for _display_ purposes: it in
    94
         * no way affects any of the arithmetic of the contract, including
    95
         * {IERC20-balanceOf} and {IERC20-transfer}.
    96
         */
    97
        function decimals() public view virtual returns (uint8) {
    98
            return 18;
    99
        }
    100
    101
        /// @inheritdoc IERC20
    102
        function totalSupply() public view virtual returns (uint256) {
    103
            ERC20Storage storage $ = _getERC20Storage();
    104
            return $._totalSupply;
    105
        }
    106
    107
        /// @inheritdoc IERC20
    108 39×
        function balanceOf(address account) public view virtual returns (uint256) {
    109
            ERC20Storage storage $ = _getERC20Storage();
    110 33×
            return $._balances[account];
    111
        }
    112
    113
        /**
    114
         * @dev See {IERC20-transfer}.
    115
         *
    116
         * Requirements:
    117
         *
    118
         * - `to` cannot be the zero address.
    119
         * - the caller must have a balance of at least `value`.
    120
         */
    121 48×
        function transfer(address to, uint256 value) public virtual returns (bool) {
    122
            address owner = _msgSender();
    123 20×
            _transfer(owner, to, value);
    124
            return true;
    125
        }
    126
    127
        /// @inheritdoc IERC20
    128 12×
        function allowance(address owner, address spender) public view virtual returns (uint256) {
    129
            ERC20Storage storage $ = _getERC20Storage();
    130 112×
            return $._allowances[owner][spender];
    131
        }
    132
    133
        /**
    134
         * @dev See {IERC20-approve}.
    135
         *
    136
         * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
    137
         * `transferFrom`. This is semantically equivalent to an infinite approval.
    138
         *
    139
         * Requirements:
    140
         *
    141
         * - `spender` cannot be the zero address.
    142
         */
    143 20×
        function approve(address spender, uint256 value) public virtual returns (bool) {
    144
            address owner = _msgSender();
    145
            _approve(owner, spender, value);
    146
            return true;
    147
        }
    148
    149
        /**
    150
         * @dev See {IERC20-transferFrom}.
    151
         *
    152
         * Skips emitting an {Approval} event indicating an allowance update. This is not
    153
         * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
    154
         *
    155
         * NOTE: Does not update the allowance if the current allowance
    156
         * is the maximum `uint256`.
    157
         *
    158
         * Requirements:
    159
         *
    160
         * - `from` and `to` cannot be the zero address.
    161
         * - `from` must have a balance of at least `value`.
    162
         * - the caller must have allowance for ``from``'s tokens of at least
    163
         * `value`.
    164
         */
    165 56×
        function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
    166
            address spender = _msgSender();
    167 24×
            _spendAllowance(from, spender, value);
    168 28×
            _transfer(from, to, value);
    169
            return true;
    170
        }
    171
    172
        /**
    173
         * @dev Moves a `value` amount of tokens from `from` to `to`.
    174
         *
    175
         * This internal function is equivalent to {transfer}, and can be used to
    176
         * e.g. implement automatic token fees, slashing mechanisms, etc.
    177
         *
    178
         * Emits a {Transfer} event.
    179
         *
    180
         * NOTE: This function is not virtual, {_update} should be overridden instead.
    181
         */
    182
        function _transfer(address from, address to, uint256 value) internal {
    183 20×
            if (from == address(0)) {
    184 0
                revert ERC20InvalidSender(address(0));
    185
            }
    186 20×
            if (to == address(0)) {
    187
                revert ERC20InvalidReceiver(address(0));
    188
            }
    189 24×
            _update(from, to, value);
    190
        }
    191
    192
        /**
    193
         * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
    194
         * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
    195
         * this function.
    196
         *
    197
         * Emits a {Transfer} event.
    198
         */
    199 24×
        function _update(address from, address to, uint256 value) internal virtual {
    200
            ERC20Storage storage $ = _getERC20Storage();
    201 30×
            if (from == address(0)) {
    202
                // Overflow check required: The rest of the code assumes that totalSupply never overflows
    203 34×
                $._totalSupply += value;
    204
            } else {
    205 56×
                uint256 fromBalance = $._balances[from];
    206 28×
                if (fromBalance < value) {
    207 24×
                    revert ERC20InsufficientBalance(from, fromBalance, value);
    208
                }
    209
                unchecked {
    210
                    // Overflow not possible: value <= fromBalance <= totalSupply.
    211 76×
                    $._balances[from] = fromBalance - value;
    212
                }
    213
            }
    214
    215 26×
            if (to == address(0)) {
    216
                unchecked {
    217
                    // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
    218 10×
                    $._totalSupply -= value;
    219
                }
    220
            } else {
    221
                unchecked {
    222
                    // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
    223 76×
                    $._balances[to] += value;
    224
                }
    225
            }
    226
    227 72×
            emit Transfer(from, to, value);
    228
        }
    229
    230
        /**
    231
         * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
    232
         * Relies on the `_update` mechanism
    233
         *
    234
         * Emits a {Transfer} event with `from` set to the zero address.
    235
         *
    236
         * NOTE: This function is not virtual, {_update} should be overridden instead.
    237
         */
    238
        function _mint(address account, uint256 value) internal {
    239 10×
            if (account == address(0)) {
    240 0
                revert ERC20InvalidReceiver(address(0));
    241
            }
    242 12×
            _update(address(0), account, value);
    243
        }
    244
    245
        /**
    246
         * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
    247
         * Relies on the `_update` mechanism.
    248
         *
    249
         * Emits a {Transfer} event with `to` set to the zero address.
    250
         *
    251
         * NOTE: This function is not virtual, {_update} should be overridden instead
    252
         */
    253
        function _burn(address account, uint256 value) internal {
    254
            if (account == address(0)) {
    255 0
                revert ERC20InvalidSender(address(0));
    256
            }
    257
            _update(account, address(0), value);
    258
        }
    259
    260
        /**
    261
         * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
    262
         *
    263
         * This internal function is equivalent to `approve`, and can be used to
    264
         * e.g. set automatic allowances for certain subsystems, etc.
    265
         *
    266
         * Emits an {Approval} event.
    267
         *
    268
         * Requirements:
    269
         *
    270
         * - `owner` cannot be the zero address.
    271
         * - `spender` cannot be the zero address.
    272
         *
    273
         * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
    274
         */
    275 17×
        function _approve(address owner, address spender, uint256 value) internal {
    276 11×
            _approve(owner, spender, value, true);
    277
        }
    278
    279
        /**
    280
         * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
    281
         *
    282
         * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
    283
         * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
    284
         * `Approval` event during `transferFrom` operations.
    285
         *
    286
         * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
    287
         * true using the following override:
    288
         *
    289
         * ```solidity
    290
         * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
    291
         *     super._approve(owner, spender, value, true);
    292
         * }
    293
         * ```
    294
         *
    295
         * Requirements are the same as {_approve}.
    296
         */
    297 28×
        function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
    298
            ERC20Storage storage $ = _getERC20Storage();
    299 20×
            if (owner == address(0)) {
    300 0
                revert ERC20InvalidApprover(address(0));
    301
            }
    302 20×
            if (spender == address(0)) {
    303
                revert ERC20InvalidSpender(address(0));
    304
            }
    305 120×
            $._allowances[owner][spender] = value;
    306 20×
            if (emitEvent) {
    307 18×
                emit Approval(owner, spender, value);
    308
            }
    309
        }
    310
    311
        /**
    312
         * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
    313
         *
    314
         * Does not update the allowance value in case of infinite allowance.
    315
         * Revert if not enough allowance is available.
    316
         *
    317
         * Does not emit an {Approval} event.
    318
         */
    319 24×
        function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
    320 36×
            uint256 currentAllowance = allowance(owner, spender);
    321 20×
            if (currentAllowance < type(uint256).max) {
    322 28×
                if (currentAllowance < value) {
    323 24×
                    revert ERC20InsufficientAllowance(spender, currentAllowance, value);
    324
                }
    325
                unchecked {
    326 40×
                    _approve(owner, spender, currentAllowance - value, false);
    327
                }
    328
            }
    329
        }
    330
    }
    100% lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
    Lines covered: 1 / 1 (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 "../proxy/utils/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 18×
            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
    }
    92% lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol
    Lines covered: 12 / 13 (92%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    import {Initializable} from "../proxy/utils/Initializable.sol";
    6
    7
    /**
    8
     * @dev Contract module that helps prevent reentrant calls to a function.
    9
     *
    10
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
    11
     * available, which can be applied to functions to make sure there are no nested
    12
     * (reentrant) calls to them.
    13
     *
    14
     * Note that because there is a single `nonReentrant` guard, functions marked as
    15
     * `nonReentrant` may not call one another. This can be worked around by making
    16
     * those functions `private`, and then adding `external` `nonReentrant` entry
    17
     * points to them.
    18
     *
    19
     * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
    20
     * consider using {ReentrancyGuardTransient} instead.
    21
     *
    22
     * TIP: If you would like to learn more about reentrancy and alternative ways
    23
     * to protect against it, check out our blog post
    24
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
    25
     */
    26
    abstract contract ReentrancyGuardUpgradeable is Initializable {
    27
        // Booleans are more expensive than uint256 or any type that takes up a full
    28
        // word because each write operation emits an extra SLOAD to first read the
    29
        // slot's contents, replace the bits taken up by the boolean, and then write
    30
        // back. This is the compiler's defense against contract upgrades and
    31
        // pointer aliasing, and it cannot be disabled.
    32
    33
        // The values being non-zero value makes deployment a bit more expensive,
    34
        // but in exchange the refund on every call to nonReentrant will be lower in
    35
        // amount. Since refunds are capped to a percentage of the total
    36
        // transaction's gas, it is best to keep them low in cases like this one, to
    37
        // increase the likelihood of the full refund coming into effect.
    38
        uint256 private constant NOT_ENTERED = 1;
    39
        uint256 private constant ENTERED = 2;
    40
    41
        /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    42
        struct ReentrancyGuardStorage {
    43
            uint256 _status;
    44
        }
    45
    46
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    47
        bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
    48
    49
        function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
    50
            assembly {
    51
                $.slot := ReentrancyGuardStorageLocation
    52
            }
    53
        }
    54
    55
        /**
    56
         * @dev Unauthorized reentrant call.
    57
         */
    58
        error ReentrancyGuardReentrantCall();
    59
    60
        function __ReentrancyGuard_init() internal onlyInitializing {
    61
            __ReentrancyGuard_init_unchained();
    62
        }
    63
    64
        function __ReentrancyGuard_init_unchained() internal onlyInitializing {
    65
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
    66
            $._status = NOT_ENTERED;
    67
        }
    68
    69
        /**
    70
         * @dev Prevents a contract from calling itself, directly or indirectly.
    71
         * Calling a `nonReentrant` function from another `nonReentrant`
    72
         * function is not supported. It is possible to prevent this from happening
    73
         * by making the `nonReentrant` function external, and making it call a
    74
         * `private` function that does the actual work.
    75
         */
    76
        modifier nonReentrant() {
    77 48×
            _nonReentrantBefore();
    78
            _;
    79 12×
            _nonReentrantAfter();
    80
        }
    81
    82
        function _nonReentrantBefore() private {
    83
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
    84
            // On the first call to nonReentrant, _status will be NOT_ENTERED
    85 24×
            if ($._status == ENTERED) {
    86 0
                revert ReentrancyGuardReentrantCall();
    87
            }
    88
    89
            // Any calls to nonReentrant after this point will fail
    90
            $._status = ENTERED;
    91
        }
    92
    93 11×
        function _nonReentrantAfter() private {
    94
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
    95
            // By storing the original value once again, a refund is triggered (see
    96
            // https://eips.ethereum.org/EIPS/eip-2200)
    97
            $._status = NOT_ENTERED;
    98
        }
    99
    100
        /**
    101
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
    102
         * `nonReentrant` function in the call stack.
    103
         */
    104
        function _reentrancyGuardEntered() internal view returns (bool) {
    105
            ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
    106
            return $._status == ENTERED;
    107
        }
    108
    }
    17% lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol
    Lines covered: 8 / 47 (17%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
    7
    import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
    8
    import {Initializable} from "../../proxy/utils/Initializable.sol";
    9
    10
    /**
    11
     * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
    12
     *
    13
     * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
    14
     * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
    15
     * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
    16
     * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
    17
     *
    18
     * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
    19
     * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
    20
     * ({_hashTypedDataV4}).
    21
     *
    22
     * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
    23
     * the chain id to protect against replay attacks on an eventual fork of the chain.
    24
     *
    25
     * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
    26
     * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
    27
     *
    28
     * NOTE: The upgradeable version of this contract does not use an immutable cache and recomputes the domain separator
    29
     * each time {_domainSeparatorV4} is called. That is cheaper than accessing a cached version in cold storage.
    30
     */
    31
    abstract contract EIP712Upgradeable is Initializable, IERC5267 {
    32
        bytes32 private constant TYPE_HASH =
    33 0
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    34
    35
        /// @custom:storage-location erc7201:openzeppelin.storage.EIP712
    36
        struct EIP712Storage {
    37
            /// @custom:oz-renamed-from _HASHED_NAME
    38
            bytes32 _hashedName;
    39
            /// @custom:oz-renamed-from _HASHED_VERSION
    40
            bytes32 _hashedVersion;
    41
    42
            string _name;
    43
            string _version;
    44
        }
    45
    46
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
    47
        bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;
    48
    49 0
        function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
    50
            assembly {
    51 0
                $.slot := EIP712StorageLocation
    52
            }
    53
        }
    54
    55
        /**
    56
         * @dev Initializes the domain separator and parameter caches.
    57
         *
    58
         * The meaning of `name` and `version` is specified in
    59
         * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
    60
         *
    61
         * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
    62
         * - `version`: the current major version of the signing domain.
    63
         *
    64
         * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
    65
         * contract upgrade].
    66
         */
    67
        function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
    68
            __EIP712_init_unchained(name, version);
    69
        }
    70
    71
        function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
    72
            EIP712Storage storage $ = _getEIP712Storage();
    73
            $._name = name;
    74
            $._version = version;
    75
    76
            // Reset prior values in storage if upgrading
    77
            $._hashedName = 0;
    78
            $._hashedVersion = 0;
    79
        }
    80
    81
        /**
    82
         * @dev Returns the domain separator for the current chain.
    83
         */
    84 0
        function _domainSeparatorV4() internal view returns (bytes32) {
    85 0
            return _buildDomainSeparator();
    86
        }
    87
    88 0
        function _buildDomainSeparator() private view returns (bytes32) {
    89 0
            return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
    90
        }
    91
    92
        /**
    93
         * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
    94
         * function returns the hash of the fully encoded EIP712 message for this domain.
    95
         *
    96
         * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
    97
         *
    98
         * ```solidity
    99
         * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
    100
         *     keccak256("Mail(address to,string contents)"),
    101
         *     mailTo,
    102
         *     keccak256(bytes(mailContents))
    103
         * )));
    104
         * address signer = ECDSA.recover(digest, signature);
    105
         * ```
    106
         */
    107 0
        function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
    108 0
            return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    109
        }
    110
    111
        /// @inheritdoc IERC5267
    112 0
        function eip712Domain()
    113
            public
    114
            view
    115
            virtual
    116
            returns (
    117 0
                bytes1 fields,
    118 0
                string memory name,
    119
                string memory version,
    120
                uint256 chainId,
    121
                address verifyingContract,
    122
                bytes32 salt,
    123
                uint256[] memory extensions
    124
            )
    125
        {
    126 0
            EIP712Storage storage $ = _getEIP712Storage();
    127
            // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
    128
            // and the EIP712 domain is not reliable, as it will be missing name and version.
    129 24×
            require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");
    130
    131 0
            return (
    132
                hex"0f", // 01111
    133 0
                _EIP712Name(),
    134 0
                _EIP712Version(),
    135 0
                block.chainid,
    136 0
                address(this),
    137 0
                bytes32(0),
    138 0
                new uint256[](0)
    139
            );
    140
        }
    141
    142
        /**
    143
         * @dev The name parameter for the EIP712 domain.
    144
         *
    145
         * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
    146
         * are a concern.
    147
         */
    148 0
        function _EIP712Name() internal view virtual returns (string memory) {
    149
            EIP712Storage storage $ = _getEIP712Storage();
    150 0
            return $._name;
    151
        }
    152
    153
        /**
    154
         * @dev The version parameter for the EIP712 domain.
    155
         *
    156
         * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
    157
         * are a concern.
    158
         */
    159 0
        function _EIP712Version() internal view virtual returns (string memory) {
    160 0
            EIP712Storage storage $ = _getEIP712Storage();
    161
            return $._version;
    162
        }
    163
    164
        /**
    165
         * @dev The hash of the name parameter for the EIP712 domain.
    166
         *
    167
         * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
    168
         */
    169 0
        function _EIP712NameHash() internal view returns (bytes32) {
    170
            EIP712Storage storage $ = _getEIP712Storage();
    171 0
            string memory name = _EIP712Name();
    172 0
            if (bytes(name).length > 0) {
    173 0
                return keccak256(bytes(name));
    174
            } else {
    175
                // If the name is empty, the contract may have been upgraded without initializing the new storage.
    176
                // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
    177 0
                bytes32 hashedName = $._hashedName;
    178 0
                if (hashedName != 0) {
    179 0
                    return hashedName;
    180
                } else {
    181 0
                    return keccak256("");
    182
                }
    183
            }
    184
        }
    185
    186
        /**
    187
         * @dev The hash of the version parameter for the EIP712 domain.
    188
         *
    189
         * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
    190
         */
    191 0
        function _EIP712VersionHash() internal view returns (bytes32) {
    192
            EIP712Storage storage $ = _getEIP712Storage();
    193 0
            string memory version = _EIP712Version();
    194 0
            if (bytes(version).length > 0) {
    195 0
                return keccak256(bytes(version));
    196
            } else {
    197
                // If the version is empty, the contract may have been upgraded without initializing the new storage.
    198
                // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
    199 0
                bytes32 hashedVersion = $._hashedVersion;
    200 0
                if (hashedVersion != 0) {
    201 0
                    return hashedVersion;
    202
                } else {
    203
                    return keccak256("");
    204
                }
    205
            }
    206
        }
    207
    }
    91% lib/setup-helpers/lib/chimera/src/CryticAsserts.sol
    Lines covered: 21 / 23 (91%)
    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 0
                emit Log(reason);
    12
                assert(false);
    13
            }
    14
        }
    15
    16
        function gte(uint256 a, uint256 b, string memory reason) internal virtual override {
    17 12×
            if (!(a >= b)) {
    18 16×
                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 12×
            if (!(a <= b)) {
    32
                emit Log(reason);
    33
                assert(false);
    34
            }
    35
        }
    36
    37
        function eq(uint256 a, uint256 b, string memory reason) internal virtual override {
    38 10×
            if (!(a == b)) {
    39 24×
                emit Log(reason);
    40
                assert(false);
    41
            }
    42
        }
    43
    44
        function t(bool b, string memory reason) internal virtual override {
    45
            if (!b) {
    46 16×
                emit Log(reason);
    47
                assert(false);
    48
            }
    49
        }
    50
    51
        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
    }
    100% lib/setup-helpers/lib/chimera/src/Hevm.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.0;
    3
    4
    // slither-disable-start shadowing-local
    5
    6
    interface IHevm {
    7
        // Set block.timestamp to newTimestamp
    8
        function warp(uint256 newTimestamp) external;
    9
    10
        // Set block.number to newNumber
    11
        function roll(uint256 newNumber) external;
    12
    13
        // Add the condition b to the assumption base for the current branch
    14
        // This function is almost identical to require
    15
        function assume(bool b) external;
    16
    17
        // Sets the eth balance of usr to amt
    18
        function deal(address usr, uint256 amt) external;
    19
    20
        // Loads a storage slot from an address
    21
        function load(address where, bytes32 slot) external returns (bytes32);
    22
    23
        // Stores a value to an address' storage slot
    24
        function store(address where, bytes32 slot, bytes32 value) external;
    25
    26
        // Signs data (privateKey, digest) => (v, r, s)
    27
        function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
    28
    29
        // Gets address for a given private key
    30
        function addr(uint256 privateKey) external returns (address addr);
    31
    32
        // Performs a foreign function call via terminal
    33
        function ffi(string[] calldata inputs) external returns (bytes memory result);
    34
    35
        // Performs the next smart contract call with specified `msg.sender`
    36
        function prank(address newSender) external;
    37
    38
        // Creates a new fork with the given endpoint and the latest block and returns the identifier of the fork
    39
        function createFork(string calldata urlOrAlias) external returns (uint256);
    40
    41
        // Takes a fork identifier created by createFork and sets the corresponding forked state as active
    42
        function selectFork(uint256 forkId) external;
    43
    44
        // Returns the identifier of the current fork
    45
        function activeFork() external returns (uint256);
    46
    47
        // Labels the address in traces
    48
        function label(address addr, string calldata label) external;
    49
    50
        /// Sets an address' code.
    51
        function etch(address target, bytes calldata newRuntimeBytecode) external;
    52
    }
    53
    54 21×
    IHevm constant vm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    55
    56
    // slither-disable-end shadowing-local
    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 351×
        function _getActor() internal view returns (address) {
    36 552×
            return _actor;
    37
        }
    38
    39
        /// @notice Returns all actors being used
    40 10×
        function _getActors() internal view returns (address[] memory) {
    41 18×
            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
    }
    93% lib/setup-helpers/src/AssetManager.sol
    Lines covered: 31 / 33 (93%)
    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 12×
            if (__asset == address(0)) {
    31 0
                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 14×
            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 53×
            address asset_ = address(new MockERC20("Test Token", "TST", decimals)); // If names get confusing, concatenate the decimals to the name
    47
            _addAsset(asset_);
    48 22×
            __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 20×
            if (_assets.contains(target)) {
    56 0
                revert Exists();
    57
            }
    58
    59 10×
            _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 15×
            for (uint256 i; i < approvalArray.length; i++) {
    91 24×
                _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 21×
            for (uint256 i; i < assets.length; i++) {
    102 27×
                for (uint256 j; j < actorsArray.length; j++) {
    103 114×
                    vm.prank(actorsArray[j]);
    104 75×
                    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 13×
            for (uint256 i; i < assets.length; i++) {
    116 14×
                for (uint256 j; j < actorsArray.length; j++) {
    117 62×
                    vm.prank(actorsArray[j]);
    118 77×
                    MockERC20(assets[i]).approve(addressToApprove, type(uint256).max);
    119
                }
    120
            }
    121
        }
    122
    }
    92% lib/setup-helpers/src/EnumerableSet.sol
    Lines covered: 23 / 25 (92%)
    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 44×
                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 36×
                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 52×
            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 21×
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
    142 66×
            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 24×
        function _values(Set storage set) private view returns (bytes32[] memory) {
    154 276×
            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 10×
        function add(AddressSet storage set, address value) internal returns (bool) {
    244 13×
            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 15×
            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 12×
        function values(AddressSet storage set) internal view returns (address[] memory) {
    294 24×
            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
    }
    73% lib/setup-helpers/src/MockERC20.sol
    Lines covered: 55 / 75 (73%)
    1
    // SPDX-License-Identifier: AGPL-3.0-only
    2
    pragma solidity ^0.8.0;
    3
    4
    /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
    5
    /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
    6
    /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
    7
    /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
    8
    abstract contract ERC20 {
    9
        /*//////////////////////////////////////////////////////////////
    10
                                     ERRORS
    11
        //////////////////////////////////////////////////////////////*/
    12
    13
        /// @notice Thrown when attempting to transfer more tokens than available balance
    14
        error InsufficientBalance(address from, uint256 balance, uint256 amount);
    15
    16
        /// @notice Thrown when attempting to transfer more tokens than allowed
    17
        error InsufficientAllowance(address owner, address spender, uint256 allowance, uint256 amount);
    18
    19
        /// @notice Thrown when minting would cause overflow
    20
        error MintOverflow(uint256 currentSupply, uint256 amount);
    21
    22
        /*//////////////////////////////////////////////////////////////
    23
                                     EVENTS
    24
        //////////////////////////////////////////////////////////////*/
    25
    26
        event Transfer(address indexed from, address indexed to, uint256 amount);
    27
    28
        event Approval(address indexed owner, address indexed spender, uint256 amount);
    29
    30
        /*//////////////////////////////////////////////////////////////
    31
                                METADATA STORAGE
    32
        //////////////////////////////////////////////////////////////*/
    33
    34 88×
        string public name;
    35
    36 0
        string public symbol;
    37
    38 72×
        uint8 public immutable decimals;
    39
    40
        /*//////////////////////////////////////////////////////////////
    41
                                  ERC20 STORAGE
    42
        //////////////////////////////////////////////////////////////*/
    43
    44 50×
        uint256 public totalSupply;
    45
    46 336×
        mapping(address => uint256) public balanceOf;
    47
    48 0
        mapping(address => mapping(address => uint256)) public allowance;
    49
    50
        /*//////////////////////////////////////////////////////////////
    51
                                EIP-2612 STORAGE
    52
        //////////////////////////////////////////////////////////////*/
    53
    54
        uint256 internal immutable INITIAL_CHAIN_ID;
    55
    56
        bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
    57
    58 0
        mapping(address => uint256) public nonces;
    59
    60
        /*//////////////////////////////////////////////////////////////
    61
                                   CONSTRUCTOR
    62
        //////////////////////////////////////////////////////////////*/
    63
    64
        constructor(string memory _name, string memory _symbol, uint8 _decimals) {
    65 36×
            name = _name;
    66 28×
            symbol = _symbol;
    67 20×
            decimals = _decimals;
    68
    69 12×
            INITIAL_CHAIN_ID = block.chainid;
    70 24×
            INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    71
        }
    72
    73
        /*//////////////////////////////////////////////////////////////
    74
                                   ERC20 LOGIC
    75
        //////////////////////////////////////////////////////////////*/
    76
    77 248×
        function approve(address spender, uint256 amount) public virtual returns (bool) {
    78 145×
            allowance[msg.sender][spender] = amount;
    79
    80 65×
            emit Approval(msg.sender, spender, amount);
    81
    82
            return true;
    83
        }
    84
    85 182×
        function transfer(address to, uint256 amount) public virtual returns (bool) {
    86 143×
            uint256 fromBalance = balanceOf[msg.sender];
    87 154×
            if (fromBalance < amount) revert InsufficientBalance(msg.sender, fromBalance, amount);
    88
    89 286×
            balanceOf[msg.sender] = fromBalance - amount;
    90
    91
            // Cannot overflow because the sum of all user
    92
            // balances can't exceed the max uint256 value.
    93
            unchecked {
    94 208×
                balanceOf[to] += amount;
    95
            }
    96
    97 169×
            emit Transfer(msg.sender, to, amount);
    98
    99 26×
            return true;
    100
        }
    101
    102 174×
        function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
    103 264×
            uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
    104 110×
            uint256 fromBalance = balanceOf[from];
    105
    106 54×
            if (allowed != type(uint256).max) {
    107 292×
                if (allowed < amount) revert InsufficientAllowance(from, msg.sender, allowed, amount);
    108 300×
                allowance[from][msg.sender] = allowed - amount;
    109
            }
    110
    111 150×
            if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount);
    112 280×
            balanceOf[from] = fromBalance - amount;
    113
    114
            // Cannot overflow because the sum of all user
    115
            // balances can't exceed the max uint256 value.
    116
            unchecked {
    117 160×
                balanceOf[to] += amount;
    118
            }
    119
    120 100×
            emit Transfer(from, to, amount);
    121
    122 20×
            return true;
    123
        }
    124
    125
        /*//////////////////////////////////////////////////////////////
    126
                                 EIP-2612 LOGIC
    127
        //////////////////////////////////////////////////////////////*/
    128
    129 0
        function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
    130
            public
    131
            virtual
    132
        {
    133 0
            require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
    134
    135
            // Unchecked because the only math done is incrementing
    136
            // the owner's nonce which cannot realistically overflow.
    137
            unchecked {
    138 0
                address recoveredAddress = ecrecover(
    139 0
                    keccak256(
    140 0
                        abi.encodePacked(
    141
                            "\x19\x01",
    142 0
                            DOMAIN_SEPARATOR(),
    143 0
                            keccak256(
    144 0
                                abi.encode(
    145 0
                                    keccak256(
    146
                                        "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
    147
                                    ),
    148
                                    owner,
    149
                                    spender,
    150
                                    value,
    151 0
                                    nonces[owner]++,
    152
                                    deadline
    153
                                )
    154
                            )
    155
                        )
    156
                    ),
    157
                    v,
    158
                    r,
    159
                    s
    160
                );
    161
    162 0
                require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
    163
    164 0
                allowance[recoveredAddress][spender] = value;
    165
            }
    166
    167 0
            emit Approval(owner, spender, value);
    168
        }
    169
    170 0
        function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
    171 0
            return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    172
        }
    173
    174 16×
        function computeDomainSeparator() internal view virtual returns (bytes32) {
    175 32×
            return keccak256(
    176 60×
                abi.encode(
    177
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
    178 72×
                    keccak256(bytes(name)),
    179
                    keccak256("1"),
    180
                    block.chainid,
    181
                    address(this)
    182
                )
    183
            );
    184
        }
    185
    186
        /*//////////////////////////////////////////////////////////////
    187
                            INTERNAL MINT/BURN LOGIC
    188
        //////////////////////////////////////////////////////////////*/
    189
    190 44×
        function _mint(address to, uint256 amount) internal virtual {
    191 96×
            uint256 newTotalSupply = totalSupply + amount;
    192 64×
            if (newTotalSupply < totalSupply) revert MintOverflow(totalSupply, amount);
    193 32×
            totalSupply = newTotalSupply;
    194
    195
            // Cannot overflow because the sum of all user
    196
            // balances can't exceed the max uint256 value.
    197
            unchecked {
    198 168×
                balanceOf[to] += amount;
    199
            }
    200
    201 88×
            emit Transfer(address(0), to, amount);
    202
        }
    203
    204
        function _burn(address from, uint256 amount) internal virtual {
    205 104×
            uint256 fromBalance = balanceOf[from];
    206 118×
            if (fromBalance < amount) revert InsufficientBalance(from, fromBalance, amount);
    207
    208 138×
            balanceOf[from] = fromBalance - amount;
    209
    210
            // Cannot underflow because a user's balance
    211
            // will never be larger than the total supply.
    212
            unchecked {
    213 54×
                totalSupply -= amount;
    214
            }
    215
    216 30×
            emit Transfer(from, address(0), amount);
    217
        }
    218
    }
    219
    220 428×
    contract MockERC20 is ERC20 {
    221 136×
        constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {}
    222
    223 44×
        function mint(address to, uint256 value) public virtual {
    224 18×
            _mint(to, value);
    225
        }
    226
    227 0
        function burn(address from, uint256 value) public virtual {
    228 0
            _burn(from, value);
    229
        }
    230
    }
    31% lib/setup-helpers/src/Utils.sol
    Lines covered: 17 / 54 (31%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Panic} from "./Panic.sol";
    5
    6 0
    contract Utils {
    7
        /// @dev check if the error returned from a call is the same as the expected error
    8
        /// @param err the error returned from a call
    9
        /// @param expected the expected error
    10
        /// @return true if the error is the same as the expected error, false otherwise
    11
        function checkError(bytes memory err, string memory expected) internal pure returns (bool) {
    12 11×
            (string memory revertMsg, bool customError) = _getRevertMsg(err);
    13
    14
            bytes32 errorBytes;
    15
            bytes32 expectedBytes;
    16
    17
            if (customError) {
    18
                // Custom error returns the keccak256 hash of the error, so don't need to hash it again
    19 32×
                errorBytes = bytes32(abi.encodePacked(revertMsg, bytes28(0)));
    20 33×
                expectedBytes = bytes4(keccak256(abi.encodePacked(expected)));
    21
            } else {
    22 0
                errorBytes = keccak256(abi.encodePacked(revertMsg));
    23 0
                expectedBytes = keccak256(abi.encodePacked(expected));
    24
            }
    25
    26
            // Check if error contains expected string
    27
            return errorBytes == expectedBytes;
    28
        }
    29
    30
        /// @dev get the revert message from a call
    31
        /// @notice based on https://ethereum.stackexchange.com/a/83577
    32
        /// @param returnData the return data from a call
    33
        /// @return the revert message and a boolean indicating if it's a custom error
    34
        function _getRevertMsg(bytes memory returnData) internal pure returns (string memory, bool) {
    35
            // If the returnData length is 0, then the transaction failed silently (without a revert message)
    36
            if (returnData.length == 0) return ("", false);
    37
    38
            // 1. Panic(uint256)
    39
            // Check that the data has the right size: 4 bytes for signature + 32 bytes for panic code
    40
            if (returnData.length == 4 + 32) {
    41
                // Check that the data starts with the Panic signature
    42 0
                bool panic = _checkIfPanic(returnData);
    43
    44 0
                if (panic) {
    45 0
                    return _getPanicCode(returnData);
    46
                }
    47
            }
    48
    49
            // Get the error selector from returnData
    50
            bytes4 errorSelector = _getErrorSelector(returnData);
    51
    52
            // 2. Error(string) - If it's a standard revert string
    53
            bytes4 errorStringSelector = bytes4(keccak256("Error(string)")); // Get the standard Error(string) selector
    54
    55
            if (errorSelector == errorStringSelector) {
    56
                assembly {
    57
                    // slice the sighash of the error so we can decode the string
    58 0
                    returnData := add(returnData, 0x04)
    59
                }
    60 0
                return (abi.decode(returnData, (string)), false);
    61
            }
    62
    63
            // 3. Custom error - Return the custom error selector as a string
    64 24×
            return (string(abi.encodePacked(errorSelector)), true);
    65
        }
    66
    67 0
        function _checkIfPanic(bytes memory returnData) internal pure returns (bool) {
    68 0
            bytes4 panicSignature = bytes4(keccak256(bytes("Panic(uint256)")));
    69
    70 0
            for (uint256 i = 0; i < 4; i++) {
    71 0
                if (returnData[i] != panicSignature[i]) {
    72 0
                    return false;
    73
                }
    74
            }
    75
    76 0
            return true;
    77
        }
    78
    79 0
        function _getPanicCode(bytes memory returnData) internal pure returns (string memory, bool) {
    80
            uint256 panicCode;
    81 0
            for (uint256 i = 4; i < 36; i++) {
    82 0
                panicCode = panicCode << 8;
    83 0
                panicCode |= uint8(returnData[i]);
    84
            }
    85
    86
            // Convert the panic code into its string representation
    87 0
            if (panicCode == 1) {
    88
                // call assert with an argument that evaluates to false
    89 0
                return (Panic.assertionPanic, false);
    90 0
            } else if (panicCode == 17) {
    91
                // arithmetic operation results in underflow or overflow
    92 0
                return (Panic.arithmeticPanic, false);
    93 0
            } else if (panicCode == 18) {
    94
                // division or modulo by zero
    95 0
                return (Panic.divisionPanic, false);
    96 0
            } else if (panicCode == 33) {
    97
                // converting a value that's too big or negative into an enum type
    98 0
                return (Panic.enumPanic, false);
    99 0
            } else if (panicCode == 34) {
    100
                // access a storage byte array that is incorrectly encoded
    101 0
                return (Panic.arrayPanic, false);
    102 0
            } else if (panicCode == 49) {
    103
                // call .pop() on an empty array
    104 0
                return (Panic.emptyArrayPanic, false);
    105 0
            } else if (panicCode == 50) {
    106
                // array access out of bounds
    107 0
                return (Panic.outOfBoundsPanic, false);
    108 0
            } else if (panicCode == 65) {
    109
                // allocate too much memory or create an array that is too large
    110 0
                return (Panic.memoryPanic, false);
    111 0
            } else if (panicCode == 81) {
    112
                // call a zero-initialized variable of internal function type
    113 0
                return (Panic.functionPanic, false);
    114
            }
    115
    116 0
            return ("Undefined panic code", false);
    117
        }
    118
    119
        function _getErrorSelector(bytes memory returnData) internal pure returns (bytes4 errorSelector) {
    120
            assembly {
    121
                errorSelector := mload(add(returnData, 0x20))
    122
            }
    123
            return errorSelector;
    124
        }
    125
    }
    30% lib/v2-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol
    Lines covered: 11 / 36 (30%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {IAccessControl} from "./IAccessControl.sol";
    7
    import {Context} from "../utils/Context.sol";
    8
    import {IERC165, ERC165} from "../utils/introspection/ERC165.sol";
    9
    10
    /**
    11
     * @dev Contract module that allows children to implement role-based access
    12
     * control mechanisms. This is a lightweight version that doesn't allow enumerating role
    13
     * members except through off-chain means by accessing the contract event logs. Some
    14
     * applications may benefit from on-chain enumerability, for those cases see
    15
     * {AccessControlEnumerable}.
    16
     *
    17
     * Roles are referred to by their `bytes32` identifier. These should be exposed
    18
     * in the external API and be unique. The best way to achieve this is by
    19
     * using `public constant` hash digests:
    20
     *
    21
     * ```solidity
    22
     * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
    23
     * ```
    24
     *
    25
     * Roles can be used to represent a set of permissions. To restrict access to a
    26
     * function call, use {hasRole}:
    27
     *
    28
     * ```solidity
    29
     * function foo() public {
    30
     *     require(hasRole(MY_ROLE, msg.sender));
    31
     *     ...
    32
     * }
    33
     * ```
    34
     *
    35
     * Roles can be granted and revoked dynamically via the {grantRole} and
    36
     * {revokeRole} functions. Each role has an associated admin role, and only
    37
     * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
    38
     *
    39
     * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
    40
     * that only accounts with this role will be able to grant or revoke other
    41
     * roles. More complex role relationships can be created by using
    42
     * {_setRoleAdmin}.
    43
     *
    44
     * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
    45
     * grant and revoke this role. Extra precautions should be taken to secure
    46
     * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
    47
     * to enforce additional security measures for this role.
    48
     */
    49
    abstract contract AccessControl is Context, IAccessControl, ERC165 {
    50
        struct RoleData {
    51
            mapping(address account => bool) hasRole;
    52
            bytes32 adminRole;
    53
        }
    54
    55
        mapping(bytes32 role => RoleData) private _roles;
    56
    57 0
        bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
    58
    59
        /**
    60
         * @dev Modifier that checks that an account has a specific role. Reverts
    61
         * with an {AccessControlUnauthorizedAccount} error including the required role.
    62
         */
    63
        modifier onlyRole(bytes32 role) {
    64 35×
            _checkRole(role);
    65
            _;
    66
        }
    67
    68
        /// @inheritdoc IERC165
    69 61×
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
    70 12×
            return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    71
        }
    72
    73
        /**
    74
         * @dev Returns `true` if `account` has been granted `role`.
    75
         */
    76
        function hasRole(bytes32 role, address account) public view virtual returns (bool) {
    77 26×
            return _roles[role].hasRole[account];
    78
        }
    79
    80
        /**
    81
         * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
    82
         * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
    83
         */
    84
        function _checkRole(bytes32 role) internal view virtual {
    85
            _checkRole(role, _msgSender());
    86
        }
    87
    88
        /**
    89
         * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
    90
         * is missing `role`.
    91
         */
    92
        function _checkRole(bytes32 role, address account) internal view virtual {
    93
            if (!hasRole(role, account)) {
    94 0
                revert AccessControlUnauthorizedAccount(account, role);
    95
            }
    96
        }
    97
    98
        /**
    99
         * @dev Returns the admin role that controls `role`. See {grantRole} and
    100
         * {revokeRole}.
    101
         *
    102
         * To change a role's admin, use {_setRoleAdmin}.
    103
         */
    104 0
        function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
    105 0
            return _roles[role].adminRole;
    106
        }
    107
    108
        /**
    109
         * @dev Grants `role` to `account`.
    110
         *
    111
         * If `account` had not been already granted `role`, emits a {RoleGranted}
    112
         * event.
    113
         *
    114
         * Requirements:
    115
         *
    116
         * - the caller must have ``role``'s admin role.
    117
         *
    118
         * May emit a {RoleGranted} event.
    119
         */
    120 0
        function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
    121 0
            _grantRole(role, account);
    122
        }
    123
    124
        /**
    125
         * @dev Revokes `role` from `account`.
    126
         *
    127
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
    128
         *
    129
         * Requirements:
    130
         *
    131
         * - the caller must have ``role``'s admin role.
    132
         *
    133
         * May emit a {RoleRevoked} event.
    134
         */
    135 0
        function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
    136 0
            _revokeRole(role, account);
    137
        }
    138
    139
        /**
    140
         * @dev Revokes `role` from the calling account.
    141
         *
    142
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
    143
         * purpose is to provide a mechanism for accounts to lose their privileges
    144
         * if they are compromised (such as when a trusted device is misplaced).
    145
         *
    146
         * If the calling account had been revoked `role`, emits a {RoleRevoked}
    147
         * event.
    148
         *
    149
         * Requirements:
    150
         *
    151
         * - the caller must be `callerConfirmation`.
    152
         *
    153
         * May emit a {RoleRevoked} event.
    154
         */
    155 0
        function renounceRole(bytes32 role, address callerConfirmation) public virtual {
    156 0
            if (callerConfirmation != _msgSender()) {
    157 0
                revert AccessControlBadConfirmation();
    158
            }
    159
    160 0
            _revokeRole(role, callerConfirmation);
    161
        }
    162
    163
        /**
    164
         * @dev Sets `adminRole` as ``role``'s admin role.
    165
         *
    166
         * Emits a {RoleAdminChanged} event.
    167
         */
    168 0
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
    169 0
            bytes32 previousAdminRole = getRoleAdmin(role);
    170 0
            _roles[role].adminRole = adminRole;
    171 0
            emit RoleAdminChanged(role, previousAdminRole, adminRole);
    172
        }
    173
    174
        /**
    175
         * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
    176
         *
    177
         * Internal function without access restriction.
    178
         *
    179
         * May emit a {RoleGranted} event.
    180
         */
    181 0
        function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
    182
            if (!hasRole(role, account)) {
    183 0
                _roles[role].hasRole[account] = true;
    184 0
                emit RoleGranted(role, account, _msgSender());
    185 0
                return true;
    186
            } else {
    187
                return false;
    188
            }
    189
        }
    190
    191
        /**
    192
         * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
    193
         *
    194
         * Internal function without access restriction.
    195
         *
    196
         * May emit a {RoleRevoked} event.
    197
         */
    198 0
        function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
    199 0
            if (hasRole(role, account)) {
    200 0
                _roles[role].hasRole[account] = false;
    201 0
                emit RoleRevoked(role, account, _msgSender());
    202 0
                return true;
    203
            } else {
    204
                return false;
    205
            }
    206
        }
    207
    }
    29% lib/v2-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol
    Lines covered: 9 / 31 (29%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.0) (proxy/Clones.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {Create2} from "../utils/Create2.sol";
    7
    import {Errors} from "../utils/Errors.sol";
    8
    9
    /**
    10
     * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
    11
     * deploying minimal proxy contracts, also known as "clones".
    12
     *
    13
     * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
    14
     * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
    15
     *
    16
     * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
    17
     * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
    18
     * deterministic method.
    19
     */
    20 0
    library Clones {
    21
        error CloneArgumentsTooLong();
    22
    23
        /**
    24
         * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
    25
         *
    26
         * This function uses the create opcode, which should never revert.
    27
         */
    28 0
        function clone(address implementation) internal returns (address instance) {
    29 0
            return clone(implementation, 0);
    30
        }
    31
    32
        /**
    33
         * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
    34
         * to the new contract.
    35
         *
    36
         * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
    37
         * to always have enough balance for new deployments. Consider exposing this function under a payable method.
    38
         */
    39 0
        function clone(address implementation, uint256 value) internal returns (address instance) {
    40 0
            if (address(this).balance < value) {
    41 0
                revert Errors.InsufficientBalance(address(this).balance, value);
    42
            }
    43
            assembly ("memory-safe") {
    44
                // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
    45
                // of the `implementation` address with the bytecode before the address.
    46 0
                mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
    47
                // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
    48 0
                mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
    49 0
                instance := create(value, 0x09, 0x37)
    50
            }
    51 0
            if (instance == address(0)) {
    52 0
                revert Errors.FailedDeployment();
    53
            }
    54
        }
    55
    56
        /**
    57
         * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
    58
         *
    59
         * This function uses the create2 opcode and a `salt` to deterministically deploy
    60
         * the clone. Using the same `implementation` and `salt` multiple times will revert, since
    61
         * the clones cannot be deployed twice at the same address.
    62
         */
    63
        function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
    64
            return cloneDeterministic(implementation, salt, 0);
    65
        }
    66
    67
        /**
    68
         * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
    69
         * a `value` parameter to send native currency to the new contract.
    70
         *
    71
         * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
    72
         * to always have enough balance for new deployments. Consider exposing this function under a payable method.
    73
         */
    74
        function cloneDeterministic(
    75
            address implementation,
    76
            bytes32 salt,
    77
            uint256 value
    78
        ) internal returns (address instance) {
    79
            if (address(this).balance < value) {
    80 0
                revert Errors.InsufficientBalance(address(this).balance, value);
    81
            }
    82
            assembly ("memory-safe") {
    83
                // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
    84
                // of the `implementation` address with the bytecode before the address.
    85
                mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
    86
                // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
    87
                mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
    88
                instance := create2(value, 0x09, 0x37, salt)
    89
            }
    90
            if (instance == address(0)) {
    91 0
                revert Errors.FailedDeployment();
    92
            }
    93
        }
    94
    95
        /**
    96
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
    97
         */
    98 0
        function predictDeterministicAddress(
    99
            address implementation,
    100
            bytes32 salt,
    101
            address deployer
    102
        ) internal pure returns (address predicted) {
    103
            assembly ("memory-safe") {
    104 0
                let ptr := mload(0x40)
    105 0
                mstore(add(ptr, 0x38), deployer)
    106 0
                mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
    107 0
                mstore(add(ptr, 0x14), implementation)
    108 0
                mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
    109 0
                mstore(add(ptr, 0x58), salt)
    110 0
                mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
    111 0
                predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
    112
            }
    113
        }
    114
    115
        /**
    116
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
    117
         */
    118
        function predictDeterministicAddress(
    119
            address implementation,
    120
            bytes32 salt
    121
        ) internal view returns (address predicted) {
    122
            return predictDeterministicAddress(implementation, salt, address(this));
    123
        }
    124
    125
        /**
    126
         * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
    127
         * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
    128
         * access the arguments within the implementation, use {fetchCloneArgs}.
    129
         *
    130
         * This function uses the create opcode, which should never revert.
    131
         */
    132
        function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
    133
            return cloneWithImmutableArgs(implementation, args, 0);
    134
        }
    135
    136
        /**
    137
         * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
    138
         * parameter to send native currency to the new contract.
    139
         *
    140
         * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
    141
         * to always have enough balance for new deployments. Consider exposing this function under a payable method.
    142
         */
    143
        function cloneWithImmutableArgs(
    144
            address implementation,
    145
            bytes memory args,
    146
            uint256 value
    147
        ) internal returns (address instance) {
    148
            if (address(this).balance < value) {
    149
                revert Errors.InsufficientBalance(address(this).balance, value);
    150
            }
    151
            bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
    152
            assembly ("memory-safe") {
    153
                instance := create(value, add(bytecode, 0x20), mload(bytecode))
    154
            }
    155
            if (instance == address(0)) {
    156
                revert Errors.FailedDeployment();
    157
            }
    158
        }
    159
    160
        /**
    161
         * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
    162
         * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
    163
         * access the arguments within the implementation, use {fetchCloneArgs}.
    164
         *
    165
         * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
    166
         * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
    167
         * at the same address.
    168
         */
    169
        function cloneDeterministicWithImmutableArgs(
    170
            address implementation,
    171
            bytes memory args,
    172
            bytes32 salt
    173
        ) internal returns (address instance) {
    174
            return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
    175
        }
    176
    177
        /**
    178
         * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
    179
         * but with a `value` parameter to send native currency to the new contract.
    180
         *
    181
         * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
    182
         * to always have enough balance for new deployments. Consider exposing this function under a payable method.
    183
         */
    184
        function cloneDeterministicWithImmutableArgs(
    185
            address implementation,
    186
            bytes memory args,
    187
            bytes32 salt,
    188
            uint256 value
    189
        ) internal returns (address instance) {
    190
            bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
    191
            return Create2.deploy(value, salt, bytecode);
    192
        }
    193
    194
        /**
    195
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
    196
         */
    197
        function predictDeterministicAddressWithImmutableArgs(
    198
            address implementation,
    199
            bytes memory args,
    200
            bytes32 salt,
    201
            address deployer
    202
        ) internal pure returns (address predicted) {
    203
            bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
    204
            return Create2.computeAddress(salt, keccak256(bytecode), deployer);
    205
        }
    206
    207
        /**
    208
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
    209
         */
    210
        function predictDeterministicAddressWithImmutableArgs(
    211
            address implementation,
    212
            bytes memory args,
    213
            bytes32 salt
    214
        ) internal view returns (address predicted) {
    215
            return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
    216
        }
    217
    218
        /**
    219
         * @dev Get the immutable args attached to a clone.
    220
         *
    221
         * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
    222
         *   function will return an empty array.
    223
         * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
    224
         *   `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
    225
         *   creation.
    226
         * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
    227
         *   function should only be used to check addresses that are known to be clones.
    228
         */
    229
        function fetchCloneArgs(address instance) internal view returns (bytes memory) {
    230
            bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
    231
            assembly ("memory-safe") {
    232
                extcodecopy(instance, add(result, 32), 45, mload(result))
    233
            }
    234
            return result;
    235
        }
    236
    237
        /**
    238
         * @dev Helper that prepares the initcode of the proxy with immutable args.
    239
         *
    240
         * An assembly variant of this function requires copying the `args` array, which can be efficiently done using
    241
         * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
    242
         * abi.encodePacked is more expensive but also more portable and easier to review.
    243
         *
    244
         * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
    245
         * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
    246
         */
    247
        function _cloneCodeWithImmutableArgs(
    248
            address implementation,
    249
            bytes memory args
    250
        ) private pure returns (bytes memory) {
    251
            if (args.length > 24531) revert CloneArgumentsTooLong();
    252
            return
    253
                abi.encodePacked(
    254
                    hex"61",
    255
                    uint16(args.length + 45),
    256
                    hex"3d81600a3d39f3363d3d373d3d3d363d73",
    257
                    implementation,
    258
                    hex"5af43d82803e903d91602b57fd5bf3",
    259
                    args
    260
                );
    261
        }
    262
    }
    94% lib/v2-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
    Lines covered: 16 / 17 (94%)
    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 "../../../interfaces/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(address spender, uint256 currentAllowance, uint256 requestedDecrease);
    28
    29
        /**
    30
         * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
    31
         * non-reverting calls are assumed to be successful.
    32
         */
    33
        function safeTransfer(IERC20 token, address to, uint256 value) internal {
    34 182×
            _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    35
        }
    36
    37
        /**
    38
         * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
    39
         * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
    40
         */
    41 19×
        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
    42 135×
            _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    43
        }
    44
    45
        /**
    46
         * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
    47
         */
    48
        function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
    49
            return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    50
        }
    51
    52
        /**
    53
         * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
    54
         */
    55
        function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
    56
            return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    57
        }
    58
    59
        /**
    60
         * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
    61
         * non-reverting calls are assumed to be successful.
    62
         *
    63
         * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
    64
         * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
    65
         * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
    66
         * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
    67
         */
    68
        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    69
            uint256 oldAllowance = token.allowance(address(this), spender);
    70
            forceApprove(token, spender, oldAllowance + value);
    71
        }
    72
    73
        /**
    74
         * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
    75
         * value, non-reverting calls are assumed to be successful.
    76
         *
    77
         * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
    78
         * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
    79
         * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
    80
         * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
    81
         */
    82
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
    83
            unchecked {
    84
                uint256 currentAllowance = token.allowance(address(this), spender);
    85
                if (currentAllowance < requestedDecrease) {
    86
                    revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
    87
                }
    88
                forceApprove(token, spender, currentAllowance - requestedDecrease);
    89
            }
    90
        }
    91
    92
        /**
    93
         * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
    94
         * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
    95
         * to be set to zero before setting it to a non-zero value, such as USDT.
    96
         *
    97
         * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
    98
         * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
    99
         * set here.
    100
         */
    101
        function forceApprove(IERC20 token, address spender, uint256 value) internal {
    102
            bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
    103
    104
            if (!_callOptionalReturnBool(token, approvalCall)) {
    105
                _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
    106
                _callOptionalReturn(token, approvalCall);
    107
            }
    108
        }
    109
    110
        /**
    111
         * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
    112
         * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
    113
         * targeting contracts.
    114
         *
    115
         * Reverts if the returned value is other than `true`.
    116
         */
    117
        function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
    118
            if (to.code.length == 0) {
    119
                safeTransfer(token, to, value);
    120
            } else if (!token.transferAndCall(to, value, data)) {
    121
                revert SafeERC20FailedOperation(address(token));
    122
            }
    123
        }
    124
    125
        /**
    126
         * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
    127
         * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
    128
         * targeting contracts.
    129
         *
    130
         * Reverts if the returned value is other than `true`.
    131
         */
    132
        function transferFromAndCallRelaxed(
    133
            IERC1363 token,
    134
            address from,
    135
            address to,
    136
            uint256 value,
    137
            bytes memory data
    138
        ) internal {
    139
            if (to.code.length == 0) {
    140
                safeTransferFrom(token, from, to, value);
    141
            } else if (!token.transferFromAndCall(from, to, value, data)) {
    142
                revert SafeERC20FailedOperation(address(token));
    143
            }
    144
        }
    145
    146
        /**
    147
         * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
    148
         * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
    149
         * targeting contracts.
    150
         *
    151
         * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
    152
         * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
    153
         * once without retrying, and relies on the returned value to be true.
    154
         *
    155
         * Reverts if the returned value is other than `true`.
    156
         */
    157
        function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
    158
            if (to.code.length == 0) {
    159
                forceApprove(token, to, value);
    160
            } else if (!token.approveAndCall(to, value, data)) {
    161
                revert SafeERC20FailedOperation(address(token));
    162
            }
    163
        }
    164
    165
        /**
    166
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    167
         * on the return value: the return value is optional (but if data is returned, it must not be false).
    168
         * @param token The token targeted by the call.
    169
         * @param data The call data (encoded using abi.encode or one of its variants).
    170
         *
    171
         * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
    172
         */
    173
        function _callOptionalReturn(IERC20 token, bytes memory data) private {
    174
            uint256 returnSize;
    175
            uint256 returnValue;
    176
            assembly ("memory-safe") {
    177 77×
                let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
    178
                // bubble errors
    179 28×
                if iszero(success) {
    180
                    let ptr := mload(0x40)
    181 12×
                    returndatacopy(ptr, 0, returndatasize())
    182
                    revert(ptr, returndatasize())
    183
                }
    184 14×
                returnSize := returndatasize()
    185 14×
                returnValue := mload(0)
    186
            }
    187
    188 103×
            if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
    189
                revert SafeERC20FailedOperation(address(token));
    190
            }
    191
        }
    192
    193
        /**
    194
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    195
         * on the return value: the return value is optional (but if data is returned, it must not be false).
    196
         * @param token The token targeted by the call.
    197
         * @param data The call data (encoded using abi.encode or one of its variants).
    198
         *
    199
         * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
    200
         */
    201
        function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
    202
            bool success;
    203
            uint256 returnSize;
    204
            uint256 returnValue;
    205
            assembly ("memory-safe") {
    206
                success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
    207
                returnSize := returndatasize()
    208
                returnValue := mload(0)
    209
            }
    210
            return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    211
        }
    212
    }
    50% lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Context.sol
    Lines covered: 1 / 2 (50%)
    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 0
        function _msgSender() internal view virtual returns (address) {
    18
            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
    }
    83% lib/v2-core/lib/openzeppelin-contracts/contracts/utils/Panic.sol
    Lines covered: 5 / 6 (83%)
    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
        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
        function panic(uint256 code) internal pure {
    51
            assembly ("memory-safe") {
    52 12×
                mstore(0x00, 0x4e487b71)
    53 12×
                mstore(0x20, code)
    54 12×
                revert(0x1c, 0x24)
    55
            }
    56
        }
    57
    }
    55% lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol
    Lines covered: 32 / 58 (55%)
    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 35×
        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 21×
                let mm := mulmod(a, b, not(0))
    43 28×
                low := mul(a, b)
    44 84×
                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 12×
                return b ^ ((a ^ b) * SafeCast.toUint(condition));
    149
            }
    150
        }
    151
    152
        /**
    153
         * @dev Returns the largest of two numbers.
    154
         */
    155
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
    156
            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 38×
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
    205
            unchecked {
    206 84×
                (uint256 high, uint256 low) = mul512(x, y);
    207
    208
                // Handle non-overflow cases, 256 by 256 division.
    209 39×
                if (high == 0) {
    210
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
    211
                    // The surrounding unchecked block does not change this fact.
    212
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
    213 86×
                    return low / denominator;
    214
                }
    215
    216
                // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
    217 20×
                if (denominator <= high) {
    218 24×
                    Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
    219
                }
    220
    221
                ///////////////////////////////////////////////
    222
                // 512 by 256 division.
    223
                ///////////////////////////////////////////////
    224
    225
                // Make division exact by subtracting the remainder from [high low].
    226
                uint256 remainder;
    227
                assembly ("memory-safe") {
    228
                    // Compute remainder using mulmod.
    229 16×
                    remainder := mulmod(x, y, denominator)
    230
    231
                    // Subtract 256 bit number from 512 bit number.
    232 24×
                    high := sub(high, gt(remainder, low))
    233 16×
                    low := sub(low, remainder)
    234
                }
    235
    236
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
    237
                // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
    238
    239 24×
                uint256 twos = denominator & (0 - denominator);
    240
                assembly ("memory-safe") {
    241
                    // Divide denominator by twos.
    242 20×
                    denominator := div(denominator, twos)
    243
    244
                    // Divide [high low] by twos.
    245 16×
                    low := div(low, twos)
    246
    247
                    // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
    248 36×
                    twos := add(div(sub(0, twos), twos), 1)
    249
                }
    250
    251
                // Shift in bits from high into low.
    252 32×
                low |= high * twos;
    253
    254
                // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
    255
                // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
    256
                // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
    257 24×
                uint256 inverse = (3 * denominator) ^ 2;
    258
    259
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
    260
                // works in modular arithmetic, doubling the correct bits in each step.
    261 24×
                inverse *= 2 - denominator * inverse; // inverse mod 2⁸
    262 24×
                inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
    263 24×
                inverse *= 2 - denominator * inverse; // inverse mod 2³²
    264 24×
                inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
    265 24×
                inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
    266 28×
                inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
    267
    268
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
    269
                // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
    270
                // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
    271
                // is no longer required.
    272
                result = low * inverse;
    273
                return result;
    274
            }
    275
        }
    276
    277
        /**
    278
         * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
    279
         */
    280 18×
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
    281 210×
            return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    282
        }
    283
    284
        /**
    285
         * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
    286
         */
    287
        function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
    288
            unchecked {
    289
                (uint256 high, uint256 low) = mul512(x, y);
    290
                if (high >= 1 << n) {
    291
                    Panic.panic(Panic.UNDER_OVERFLOW);
    292
                }
    293
                return (high << (256 - n)) | (low >> n);
    294
            }
    295
        }
    296
    297
        /**
    298
         * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
    299
         */
    300
        function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
    301
            return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
    302
        }
    303
    304
        /**
    305
         * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
    306
         *
    307
         * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
    308
         * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
    309
         *
    310
         * If the input value is not inversible, 0 is returned.
    311
         *
    312
         * 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
    313
         * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
    314
         */
    315
        function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
    316
            unchecked {
    317
                if (n == 0) return 0;
    318
    319
                // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
    320
                // Used to compute integers x and y such that: ax + ny = gcd(a, n).
    321
                // When the gcd is 1, then the inverse of a modulo n exists and it's x.
    322
                // ax + ny = 1
    323
                // ax = 1 + (-y)n
    324
                // ax ≡ 1 (mod n) # x is the inverse of a modulo n
    325
    326
                // If the remainder is 0 the gcd is n right away.
    327
                uint256 remainder = a % n;
    328
                uint256 gcd = n;
    329
    330
                // Therefore the initial coefficients are:
    331
                // ax + ny = gcd(a, n) = n
    332
                // 0a + 1n = n
    333
                int256 x = 0;
    334
                int256 y = 1;
    335
    336
                while (remainder != 0) {
    337
                    uint256 quotient = gcd / remainder;
    338
    339
                    (gcd, remainder) = (
    340
                        // The old remainder is the next gcd to try.
    341
                        remainder,
    342
                        // Compute the next remainder.
    343
                        // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
    344
                        // where gcd is at most n (capped to type(uint256).max)
    345
                        gcd - remainder * quotient
    346
                    );
    347
    348
                    (x, y) = (
    349
                        // Increment the coefficient of a.
    350
                        y,
    351
                        // Decrement the coefficient of n.
    352
                        // Can overflow, but the result is casted to uint256 so that the
    353
                        // next value of y is "wrapped around" to a value between 0 and n - 1.
    354
                        x - y * int256(quotient)
    355
                    );
    356
                }
    357
    358
                if (gcd != 1) return 0; // No inverse exists.
    359
                return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
    360
            }
    361
        }
    362
    363
        /**
    364
         * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
    365
         *
    366
         * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
    367
         * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
    368
         * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
    369
         *
    370
         * NOTE: this function does NOT check that `p` is a prime greater than `2`.
    371
         */
    372
        function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
    373
            unchecked {
    374
                return Math.modExp(a, p - 2, p);
    375
            }
    376
        }
    377
    378
        /**
    379
         * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
    380
         *
    381
         * Requirements:
    382
         * - modulus can't be zero
    383
         * - underlying staticcall to precompile must succeed
    384
         *
    385
         * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
    386
         * sure the chain you're using it on supports the precompiled contract for modular exponentiation
    387
         * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
    388
         * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
    389
         * interpreted as 0.
    390
         */
    391
        function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
    392
            (bool success, uint256 result) = tryModExp(b, e, m);
    393
            if (!success) {
    394
                Panic.panic(Panic.DIVISION_BY_ZERO);
    395
            }
    396
            return result;
    397
        }
    398
    399
        /**
    400
         * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
    401
         * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
    402
         * to operate modulo 0 or if the underlying precompile reverted.
    403
         *
    404
         * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
    405
         * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
    406
         * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
    407
         * of a revert, but the result may be incorrectly interpreted as 0.
    408
         */
    409
        function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
    410
            if (m == 0) return (false, 0);
    411
            assembly ("memory-safe") {
    412
                let ptr := mload(0x40)
    413
                // | Offset    | Content    | Content (Hex)                                                      |
    414
                // |-----------|------------|--------------------------------------------------------------------|
    415
                // | 0x00:0x1f | size of b  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
    416
                // | 0x20:0x3f | size of e  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
    417
                // | 0x40:0x5f | size of m  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
    418
                // | 0x60:0x7f | value of b | 0x<.............................................................b> |
    419
                // | 0x80:0x9f | value of e | 0x<.............................................................e> |
    420
                // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
    421
                mstore(ptr, 0x20)
    422
                mstore(add(ptr, 0x20), 0x20)
    423
                mstore(add(ptr, 0x40), 0x20)
    424
                mstore(add(ptr, 0x60), b)
    425
                mstore(add(ptr, 0x80), e)
    426
                mstore(add(ptr, 0xa0), m)
    427
    428
                // Given the result < m, it's guaranteed to fit in 32 bytes,
    429
                // so we can use the memory scratch space located at offset 0.
    430
                success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
    431
                result := mload(0x00)
    432
            }
    433
        }
    434
    435
        /**
    436
         * @dev Variant of {modExp} that supports inputs of arbitrary length.
    437
         */
    438
        function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
    439
            (bool success, bytes memory result) = tryModExp(b, e, m);
    440
            if (!success) {
    441
                Panic.panic(Panic.DIVISION_BY_ZERO);
    442
            }
    443
            return result;
    444
        }
    445
    446
        /**
    447
         * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
    448
         */
    449
        function tryModExp(
    450
            bytes memory b,
    451
            bytes memory e,
    452
            bytes memory m
    453
        ) internal view returns (bool success, bytes memory result) {
    454
            if (_zeroBytes(m)) return (false, new bytes(0));
    455
    456
            uint256 mLen = m.length;
    457
    458
            // Encode call args in result and move the free memory pointer
    459
            result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
    460
    461
            assembly ("memory-safe") {
    462
                let dataPtr := add(result, 0x20)
    463
                // Write result on top of args to avoid allocating extra memory.
    464
                success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
    465
                // Overwrite the length.
    466
                // result.length > returndatasize() is guaranteed because returndatasize() == m.length
    467
                mstore(result, mLen)
    468
                // Set the memory pointer after the returned data.
    469
                mstore(0x40, add(dataPtr, mLen))
    470
            }
    471
        }
    472
    473
        /**
    474
         * @dev Returns whether the provided byte array is zero.
    475
         */
    476
        function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
    477
            for (uint256 i = 0; i < byteArray.length; ++i) {
    478
                if (byteArray[i] != 0) {
    479
                    return false;
    480
                }
    481
            }
    482
            return true;
    483
        }
    484
    485
        /**
    486
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
    487
         * towards zero.
    488
         *
    489
         * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
    490
         * using integer operations.
    491
         */
    492
        function sqrt(uint256 a) internal pure returns (uint256) {
    493
            unchecked {
    494
                // Take care of easy edge cases when a == 0 or a == 1
    495
                if (a <= 1) {
    496
                    return a;
    497
                }
    498
    499
                // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
    500
                // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
    501
                // the current value as `ε_n = | x_n - sqrt(a) |`.
    502
                //
    503
                // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
    504
                // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
    505
                // bigger than any uint256.
    506
                //
    507
                // By noticing that
    508
                // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
    509
                // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
    510
                // to the msb function.
    511
                uint256 aa = a;
    512
                uint256 xn = 1;
    513
    514
                if (aa >= (1 << 128)) {
    515
                    aa >>= 128;
    516
                    xn <<= 64;
    517
                }
    518
                if (aa >= (1 << 64)) {
    519
                    aa >>= 64;
    520
                    xn <<= 32;
    521
                }
    522
                if (aa >= (1 << 32)) {
    523
                    aa >>= 32;
    524
                    xn <<= 16;
    525
                }
    526
                if (aa >= (1 << 16)) {
    527
                    aa >>= 16;
    528
                    xn <<= 8;
    529
                }
    530
                if (aa >= (1 << 8)) {
    531
                    aa >>= 8;
    532
                    xn <<= 4;
    533
                }
    534
                if (aa >= (1 << 4)) {
    535
                    aa >>= 4;
    536
                    xn <<= 2;
    537
                }
    538
                if (aa >= (1 << 2)) {
    539
                    xn <<= 1;
    540
                }
    541
    542
                // 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).
    543
                //
    544
                // We can refine our estimation by noticing that the middle of that interval minimizes the error.
    545
                // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
    546
                // This is going to be our x_0 (and ε_0)
    547
                xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
    548
    549
                // From here, Newton's method give us:
    550
                // x_{n+1} = (x_n + a / x_n) / 2
    551
                //
    552
                // One should note that:
    553
                // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
    554
                //              = ((x_n² + a) / (2 * x_n))² - a
    555
                //              = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
    556
                //              = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
    557
                //              = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
    558
                //              = (x_n² - a)² / (2 * x_n)²
    559
                //              = ((x_n² - a) / (2 * x_n))²
    560
                //              ≥ 0
    561
                // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
    562
                //
    563
                // This gives us the proof of quadratic convergence of the sequence:
    564
                // ε_{n+1} = | x_{n+1} - sqrt(a) |
    565
                //         = | (x_n + a / x_n) / 2 - sqrt(a) |
    566
                //         = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
    567
                //         = | (x_n - sqrt(a))² / (2 * x_n) |
    568
                //         = | ε_n² / (2 * x_n) |
    569
                //         = ε_n² / | (2 * x_n) |
    570
                //
    571
                // For the first iteration, we have a special case where x_0 is known:
    572
                // ε_1 = ε_0² / | (2 * x_0) |
    573
                //     ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
    574
                //     ≤ 2**(2*e-4) / (3 * 2**(e-1))
    575
                //     ≤ 2**(e-3) / 3
    576
                //     ≤ 2**(e-3-log2(3))
    577
                //     ≤ 2**(e-4.5)
    578
                //
    579
                // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
    580
                // ε_{n+1} = ε_n² / | (2 * x_n) |
    581
                //         ≤ (2**(e-k))² / (2 * 2**(e-1))
    582
                //         ≤ 2**(2*e-2*k) / 2**e
    583
                //         ≤ 2**(e-2*k)
    584
                xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5)  -- special case, see above
    585
                xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9)    -- general case with k = 4.5
    586
                xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18)   -- general case with k = 9
    587
                xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36)   -- general case with k = 18
    588
                xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72)   -- general case with k = 36
    589
                xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144)  -- general case with k = 72
    590
    591
                // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
    592
                // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
    593
                // sqrt(a) or sqrt(a) + 1.
    594
                return xn - SafeCast.toUint(xn > a / xn);
    595
            }
    596
        }
    597
    598
        /**
    599
         * @dev Calculates sqrt(a), following the selected rounding direction.
    600
         */
    601
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
    602
            unchecked {
    603
                uint256 result = sqrt(a);
    604
                return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
    605
            }
    606
        }
    607
    608
        /**
    609
         * @dev Return the log in base 2 of a positive value rounded towards zero.
    610
         * Returns 0 if given 0.
    611
         */
    612
        function log2(uint256 x) internal pure returns (uint256 r) {
    613
            // If value has upper 128 bits set, log2 result is at least 128
    614
            r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
    615
            // If upper 64 bits of 128-bit half set, add 64 to result
    616
            r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
    617
            // If upper 32 bits of 64-bit half set, add 32 to result
    618
            r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
    619
            // If upper 16 bits of 32-bit half set, add 16 to result
    620
            r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
    621
            // If upper 8 bits of 16-bit half set, add 8 to result
    622
            r |= SafeCast.toUint((x >> r) > 0xff) << 3;
    623
            // If upper 4 bits of 8-bit half set, add 4 to result
    624
            r |= SafeCast.toUint((x >> r) > 0xf) << 2;
    625
    626
            // Shifts value right by the current result and use it as an index into this lookup table:
    627
            //
    628
            // | x (4 bits) |  index  | table[index] = MSB position |
    629
            // |------------|---------|-----------------------------|
    630
            // |    0000    |    0    |        table[0] = 0         |
    631
            // |    0001    |    1    |        table[1] = 0         |
    632
            // |    0010    |    2    |        table[2] = 1         |
    633
            // |    0011    |    3    |        table[3] = 1         |
    634
            // |    0100    |    4    |        table[4] = 2         |
    635
            // |    0101    |    5    |        table[5] = 2         |
    636
            // |    0110    |    6    |        table[6] = 2         |
    637
            // |    0111    |    7    |        table[7] = 2         |
    638
            // |    1000    |    8    |        table[8] = 3         |
    639
            // |    1001    |    9    |        table[9] = 3         |
    640
            // |    1010    |   10    |        table[10] = 3        |
    641
            // |    1011    |   11    |        table[11] = 3        |
    642
            // |    1100    |   12    |        table[12] = 3        |
    643
            // |    1101    |   13    |        table[13] = 3        |
    644
            // |    1110    |   14    |        table[14] = 3        |
    645
            // |    1111    |   15    |        table[15] = 3        |
    646
            //
    647
            // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
    648
            assembly ("memory-safe") {
    649
                r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
    650
            }
    651
        }
    652
    653
        /**
    654
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
    655
         * Returns 0 if given 0.
    656
         */
    657
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
    658
            unchecked {
    659
                uint256 result = log2(value);
    660
                return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
    661
            }
    662
        }
    663
    664
        /**
    665
         * @dev Return the log in base 10 of a positive value rounded towards zero.
    666
         * Returns 0 if given 0.
    667
         */
    668 0
        function log10(uint256 value) internal pure returns (uint256) {
    669
            uint256 result = 0;
    670
            unchecked {
    671 0
                if (value >= 10 ** 64) {
    672 0
                    value /= 10 ** 64;
    673 0
                    result += 64;
    674
                }
    675 0
                if (value >= 10 ** 32) {
    676 0
                    value /= 10 ** 32;
    677 0
                    result += 32;
    678
                }
    679 0
                if (value >= 10 ** 16) {
    680 0
                    value /= 10 ** 16;
    681 0
                    result += 16;
    682
                }
    683 0
                if (value >= 10 ** 8) {
    684 0
                    value /= 10 ** 8;
    685 0
                    result += 8;
    686
                }
    687 0
                if (value >= 10 ** 4) {
    688 0
                    value /= 10 ** 4;
    689 0
                    result += 4;
    690
                }
    691 0
                if (value >= 10 ** 2) {
    692 0
                    value /= 10 ** 2;
    693 0
                    result += 2;
    694
                }
    695 0
                if (value >= 10 ** 1) {
    696 0
                    result += 1;
    697
                }
    698
            }
    699 0
            return result;
    700
        }
    701
    702
        /**
    703
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
    704
         * Returns 0 if given 0.
    705
         */
    706
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
    707
            unchecked {
    708
                uint256 result = log10(value);
    709
                return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
    710
            }
    711
        }
    712
    713
        /**
    714
         * @dev Return the log in base 256 of a positive value rounded towards zero.
    715
         * Returns 0 if given 0.
    716
         *
    717
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
    718
         */
    719
        function log256(uint256 x) internal pure returns (uint256 r) {
    720
            // If value has upper 128 bits set, log2 result is at least 128
    721
            r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
    722
            // If upper 64 bits of 128-bit half set, add 64 to result
    723
            r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
    724
            // If upper 32 bits of 64-bit half set, add 32 to result
    725
            r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
    726
            // If upper 16 bits of 32-bit half set, add 16 to result
    727
            r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
    728
            // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
    729
            return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
    730
        }
    731
    732
        /**
    733
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
    734
         * Returns 0 if given 0.
    735
         */
    736
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
    737
            unchecked {
    738
                uint256 result = log256(value);
    739
                return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
    740
            }
    741
        }
    742
    743
        /**
    744
         * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
    745
         */
    746 42×
        function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
    747 147×
            return uint8(rounding) % 2 == 1;
    748
        }
    749
    }
    9% lib/v2-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
    Lines covered: 2 / 22 (9%)
    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
                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
        function toUint192(uint256 value) internal pure returns (uint192) {
    170
            if (value > type(uint192).max) {
    171
                revert SafeCastOverflowedUintDowncast(192, value);
    172
            }
    173
            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 0
        function toUint160(uint256 value) internal pure returns (uint160) {
    238 0
            if (value > type(uint160).max) {
    239 0
                revert SafeCastOverflowedUintDowncast(160, value);
    240
            }
    241 0
            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
        function toUint120(uint256 value) internal pure returns (uint120) {
    323
            if (value > type(uint120).max) {
    324
                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
            if (value > type(uint96).max) {
    375
                revert SafeCastOverflowedUintDowncast(96, value);
    376
            }
    377
            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
        function toUint80(uint256 value) internal pure returns (uint80) {
    408
            if (value > type(uint80).max) {
    409
                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 0
        function toUint48(uint256 value) internal pure returns (uint48) {
    476 0
            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
            if (value > type(uint40).max) {
    494
                revert SafeCastOverflowedUintDowncast(40, value);
    495
            }
    496
            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
        function toUint32(uint256 value) internal pure returns (uint32) {
    510
            if (value > type(uint32).max) {
    511
                revert SafeCastOverflowedUintDowncast(32, value);
    512
            }
    513
            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
                revert SafeCastOverflowedUintDowncast(24, value);
    529
            }
    530
            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
        function toUint16(uint256 value) internal pure returns (uint16) {
    544
            if (value > type(uint16).max) {
    545
                revert SafeCastOverflowedUintDowncast(16, value);
    546
            }
    547
            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
        function toUint8(uint256 value) internal pure returns (uint8) {
    561
            if (value > type(uint8).max) {
    562
                revert SafeCastOverflowedUintDowncast(8, value);
    563
            }
    564
            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
        function toUint256(int256 value) internal pure returns (uint256) {
    575
            if (value < 0) {
    576
                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
            if (downcasted != value) {
    703
                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 0
        function toInt128(int256 value) internal pure returns (int128 downcasted) {
    863 0
            downcasted = int128(value);
    864 0
            if (downcasted != value) {
    865 0
                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
        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
            if (value > uint256(type(int256).max)) {
    1149
                revert SafeCastOverflowedUintToInt(value);
    1150
            }
    1151
            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
        function toUint(bool b) internal pure returns (uint256 u) {
    1158
            assembly ("memory-safe") {
    1159 21×
                u := iszero(iszero(b))
    1160
            }
    1161
        }
    1162
    }
    87% lib/v2-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
    Lines covered: 35 / 40 (87%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.3.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/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 66×
                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 54×
                set._positions[value] = set._values.length;
    80
                return true;
    81
            } else {
    82
                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
        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 48×
            uint256 position = set._positions[value];
    95
    96 17×
            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 36×
                uint256 valueIndex = position - 1;
    103 52×
                uint256 lastIndex = set._values.length - 1;
    104
    105 24×
                if (valueIndex != lastIndex) {
    106 88×
                    bytes32 lastValue = set._values[lastIndex];
    107
    108
                    // Move the lastValue to the index where the value to delete is
    109 108×
                    set._values[valueIndex] = lastValue;
    110
                    // Update the tracked position of the lastValue (that was just moved)
    111 56×
                    set._positions[lastValue] = position;
    112
                }
    113
    114
                // Delete the slot where the moved value was stored
    115 100×
                set._values.pop();
    116
    117
                // Delete the tracked position for the deleted slot
    118 76×
                delete set._positions[value];
    119
    120 32×
                return true;
    121
            } else {
    122
                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 162×
            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 0
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
    166 0
            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 12×
        function _values(Set storage set) private view returns (bytes32[] memory) {
    178 138×
            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
        function _values(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) {
    190
            unchecked {
    191
                end = Math.min(end, _length(set));
    192
                start = Math.min(start, end);
    193
    194
                uint256 len = end - start;
    195
                bytes32[] memory result = new bytes32[](len);
    196
                for (uint256 i = 0; i < len; ++i) {
    197
                    result[i] = Arrays.unsafeAccess(set._values, start + i).value;
    198
                }
    199
                return result;
    200
            }
    201
        }
    202
    203
        // Bytes32Set
    204
    205
        struct Bytes32Set {
    206
            Set _inner;
    207
        }
    208
    209
        /**
    210
         * @dev Add a value to a set. O(1).
    211
         *
    212
         * Returns true if the value was added to the set, that is if it was not
    213
         * already present.
    214
         */
    215
        function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    216
            return _add(set._inner, value);
    217
        }
    218
    219
        /**
    220
         * @dev Removes a value from a set. O(1).
    221
         *
    222
         * Returns true if the value was removed from the set, that is if it was
    223
         * present.
    224
         */
    225
        function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    226
            return _remove(set._inner, value);
    227
        }
    228
    229
        /**
    230
         * @dev Removes all the values from a set. O(n).
    231
         *
    232
         * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    233
         * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    234
         */
    235
        function clear(Bytes32Set storage set) internal {
    236
            _clear(set._inner);
    237
        }
    238
    239
        /**
    240
         * @dev Returns true if the value is in the set. O(1).
    241
         */
    242
        function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
    243
            return _contains(set._inner, value);
    244
        }
    245
    246
        /**
    247
         * @dev Returns the number of values in the set. O(1).
    248
         */
    249
        function length(Bytes32Set storage set) internal view returns (uint256) {
    250
            return _length(set._inner);
    251
        }
    252
    253
        /**
    254
         * @dev Returns the value stored at position `index` in the set. O(1).
    255
         *
    256
         * Note that there are no guarantees on the ordering of values inside the
    257
         * array, and it may change when more values are added or removed.
    258
         *
    259
         * Requirements:
    260
         *
    261
         * - `index` must be strictly less than {length}.
    262
         */
    263
        function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
    264
            return _at(set._inner, index);
    265
        }
    266
    267
        /**
    268
         * @dev Return the entire set in an array
    269
         *
    270
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    271
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    272
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    273
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    274
         */
    275
        function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
    276
            bytes32[] memory store = _values(set._inner);
    277
            bytes32[] memory result;
    278
    279
            assembly ("memory-safe") {
    280
                result := store
    281
            }
    282
    283
            return result;
    284
        }
    285
    286
        /**
    287
         * @dev Return a slice of the set in an array
    288
         *
    289
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    290
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    291
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    292
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    293
         */
    294
        function values(Bytes32Set storage set, uint256 start, uint256 end) internal view returns (bytes32[] memory) {
    295
            bytes32[] memory store = _values(set._inner, start, end);
    296
            bytes32[] memory result;
    297
    298
            assembly ("memory-safe") {
    299
                result := store
    300
            }
    301
    302
            return result;
    303
        }
    304
    305
        // AddressSet
    306
    307
        struct AddressSet {
    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(AddressSet storage set, address value) internal returns (bool) {
    318 18×
            return _add(set._inner, bytes32(uint256(uint160(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(AddressSet storage set, address value) internal returns (bool) {
    328 24×
            return _remove(set._inner, bytes32(uint256(uint160(value))));
    329
        }
    330
    331
        /**
    332
         * @dev Removes all the values from a set. O(n).
    333
         *
    334
         * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    335
         * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    336
         */
    337
        function clear(AddressSet storage set) internal {
    338
            _clear(set._inner);
    339
        }
    340
    341
        /**
    342
         * @dev Returns true if the value is in the set. O(1).
    343
         */
    344 26×
        function contains(AddressSet storage set, address value) internal view returns (bool) {
    345 35×
            return _contains(set._inner, bytes32(uint256(uint160(value))));
    346
        }
    347
    348
        /**
    349
         * @dev Returns the number of values in the set. O(1).
    350
         */
    351
        function length(AddressSet storage set) internal view returns (uint256) {
    352
            return _length(set._inner);
    353
        }
    354
    355
        /**
    356
         * @dev Returns the value stored at position `index` in the set. O(1).
    357
         *
    358
         * Note that there are no guarantees on the ordering of values inside the
    359
         * array, and it may change when more values are added or removed.
    360
         *
    361
         * Requirements:
    362
         *
    363
         * - `index` must be strictly less than {length}.
    364
         */
    365 0
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
    366 0
            return address(uint160(uint256(_at(set._inner, index))));
    367
        }
    368
    369
        /**
    370
         * @dev Return the entire set in an array
    371
         *
    372
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    373
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    374
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    375
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    376
         */
    377 12×
        function values(AddressSet storage set) internal view returns (address[] memory) {
    378 18×
            bytes32[] memory store = _values(set._inner);
    379
            address[] memory result;
    380
    381
            assembly ("memory-safe") {
    382
                result := store
    383
            }
    384
    385
            return result;
    386
        }
    387
    388
        /**
    389
         * @dev Return a slice of the set in an array
    390
         *
    391
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    392
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    393
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    394
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    395
         */
    396
        function values(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) {
    397
            bytes32[] memory store = _values(set._inner, start, end);
    398
            address[] memory result;
    399
    400
            assembly ("memory-safe") {
    401
                result := store
    402
            }
    403
    404
            return result;
    405
        }
    406
    407
        // UintSet
    408
    409
        struct UintSet {
    410
            Set _inner;
    411
        }
    412
    413
        /**
    414
         * @dev Add a value to a set. O(1).
    415
         *
    416
         * Returns true if the value was added to the set, that is if it was not
    417
         * already present.
    418
         */
    419
        function add(UintSet storage set, uint256 value) internal returns (bool) {
    420
            return _add(set._inner, bytes32(value));
    421
        }
    422
    423
        /**
    424
         * @dev Removes a value from a set. O(1).
    425
         *
    426
         * Returns true if the value was removed from the set, that is if it was
    427
         * present.
    428
         */
    429
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
    430
            return _remove(set._inner, bytes32(value));
    431
        }
    432
    433
        /**
    434
         * @dev Removes all the values from a set. O(n).
    435
         *
    436
         * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    437
         * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    438
         */
    439
        function clear(UintSet storage set) internal {
    440
            _clear(set._inner);
    441
        }
    442
    443
        /**
    444
         * @dev Returns true if the value is in the set. O(1).
    445
         */
    446
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
    447
            return _contains(set._inner, bytes32(value));
    448
        }
    449
    450
        /**
    451
         * @dev Returns the number of values in the set. O(1).
    452
         */
    453
        function length(UintSet storage set) internal view returns (uint256) {
    454
            return _length(set._inner);
    455
        }
    456
    457
        /**
    458
         * @dev Returns the value stored at position `index` in the set. O(1).
    459
         *
    460
         * Note that there are no guarantees on the ordering of values inside the
    461
         * array, and it may change when more values are added or removed.
    462
         *
    463
         * Requirements:
    464
         *
    465
         * - `index` must be strictly less than {length}.
    466
         */
    467
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
    468
            return uint256(_at(set._inner, index));
    469
        }
    470
    471
        /**
    472
         * @dev Return the entire set in an array
    473
         *
    474
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    475
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    476
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    477
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    478
         */
    479
        function values(UintSet storage set) internal view returns (uint256[] memory) {
    480
            bytes32[] memory store = _values(set._inner);
    481
            uint256[] memory result;
    482
    483
            assembly ("memory-safe") {
    484
                result := store
    485
            }
    486
    487
            return result;
    488
        }
    489
    490
        /**
    491
         * @dev Return a slice of the set in an array
    492
         *
    493
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    494
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    495
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    496
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    497
         */
    498
        function values(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) {
    499
            bytes32[] memory store = _values(set._inner, start, end);
    500
            uint256[] memory result;
    501
    502
            assembly ("memory-safe") {
    503
                result := store
    504
            }
    505
    506
            return result;
    507
        }
    508
    509
        struct StringSet {
    510
            // Storage of set values
    511
            string[] _values;
    512
            // Position is the index of the value in the `values` array plus 1.
    513
            // Position 0 is used to mean a value is not in the set.
    514
            mapping(string value => uint256) _positions;
    515
        }
    516
    517
        /**
    518
         * @dev Add a value to a set. O(1).
    519
         *
    520
         * Returns true if the value was added to the set, that is if it was not
    521
         * already present.
    522
         */
    523
        function add(StringSet storage set, string memory value) internal returns (bool) {
    524
            if (!contains(set, value)) {
    525
                set._values.push(value);
    526
                // The value is stored at length-1, but we add 1 to all indexes
    527
                // and use 0 as a sentinel value
    528
                set._positions[value] = set._values.length;
    529
                return true;
    530
            } else {
    531
                return false;
    532
            }
    533
        }
    534
    535
        /**
    536
         * @dev Removes a value from a set. O(1).
    537
         *
    538
         * Returns true if the value was removed from the set, that is if it was
    539
         * present.
    540
         */
    541
        function remove(StringSet storage set, string memory value) internal returns (bool) {
    542
            // We cache the value's position to prevent multiple reads from the same storage slot
    543
            uint256 position = set._positions[value];
    544
    545
            if (position != 0) {
    546
                // Equivalent to contains(set, value)
    547
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    548
                // the array, and then remove the last element (sometimes called as 'swap and pop').
    549
                // This modifies the order of the array, as noted in {at}.
    550
    551
                uint256 valueIndex = position - 1;
    552
                uint256 lastIndex = set._values.length - 1;
    553
    554
                if (valueIndex != lastIndex) {
    555
                    string memory lastValue = set._values[lastIndex];
    556
    557
                    // Move the lastValue to the index where the value to delete is
    558
                    set._values[valueIndex] = lastValue;
    559
                    // Update the tracked position of the lastValue (that was just moved)
    560
                    set._positions[lastValue] = position;
    561
                }
    562
    563
                // Delete the slot where the moved value was stored
    564
                set._values.pop();
    565
    566
                // Delete the tracked position for the deleted slot
    567
                delete set._positions[value];
    568
    569
                return true;
    570
            } else {
    571
                return false;
    572
            }
    573
        }
    574
    575
        /**
    576
         * @dev Removes all the values from a set. O(n).
    577
         *
    578
         * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    579
         * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    580
         */
    581
        function clear(StringSet storage set) internal {
    582
            uint256 len = length(set);
    583
            for (uint256 i = 0; i < len; ++i) {
    584
                delete set._positions[set._values[i]];
    585
            }
    586
            Arrays.unsafeSetLength(set._values, 0);
    587
        }
    588
    589
        /**
    590
         * @dev Returns true if the value is in the set. O(1).
    591
         */
    592
        function contains(StringSet storage set, string memory value) internal view returns (bool) {
    593
            return set._positions[value] != 0;
    594
        }
    595
    596
        /**
    597
         * @dev Returns the number of values on the set. O(1).
    598
         */
    599
        function length(StringSet storage set) internal view returns (uint256) {
    600
            return set._values.length;
    601
        }
    602
    603
        /**
    604
         * @dev Returns the value stored at position `index` in the set. O(1).
    605
         *
    606
         * Note that there are no guarantees on the ordering of values inside the
    607
         * array, and it may change when more values are added or removed.
    608
         *
    609
         * Requirements:
    610
         *
    611
         * - `index` must be strictly less than {length}.
    612
         */
    613
        function at(StringSet storage set, uint256 index) internal view returns (string memory) {
    614
            return set._values[index];
    615
        }
    616
    617
        /**
    618
         * @dev Return the entire set in an array
    619
         *
    620
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    621
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    622
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    623
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    624
         */
    625
        function values(StringSet storage set) internal view returns (string[] memory) {
    626
            return set._values;
    627
        }
    628
    629
        /**
    630
         * @dev Return a slice of the set in an array
    631
         *
    632
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    633
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    634
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    635
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    636
         */
    637
        function values(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) {
    638
            unchecked {
    639
                end = Math.min(end, length(set));
    640
                start = Math.min(start, end);
    641
    642
                uint256 len = end - start;
    643
                string[] memory result = new string[](len);
    644
                for (uint256 i = 0; i < len; ++i) {
    645
                    result[i] = Arrays.unsafeAccess(set._values, start + i).value;
    646
                }
    647
                return result;
    648
            }
    649
        }
    650
    651
        struct BytesSet {
    652
            // Storage of set values
    653
            bytes[] _values;
    654
            // Position is the index of the value in the `values` array plus 1.
    655
            // Position 0 is used to mean a value is not in the set.
    656
            mapping(bytes value => uint256) _positions;
    657
        }
    658
    659
        /**
    660
         * @dev Add a value to a set. O(1).
    661
         *
    662
         * Returns true if the value was added to the set, that is if it was not
    663
         * already present.
    664
         */
    665
        function add(BytesSet storage set, bytes memory value) internal returns (bool) {
    666
            if (!contains(set, value)) {
    667
                set._values.push(value);
    668
                // The value is stored at length-1, but we add 1 to all indexes
    669
                // and use 0 as a sentinel value
    670
                set._positions[value] = set._values.length;
    671
                return true;
    672
            } else {
    673
                return false;
    674
            }
    675
        }
    676
    677
        /**
    678
         * @dev Removes a value from a set. O(1).
    679
         *
    680
         * Returns true if the value was removed from the set, that is if it was
    681
         * present.
    682
         */
    683
        function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
    684
            // We cache the value's position to prevent multiple reads from the same storage slot
    685
            uint256 position = set._positions[value];
    686
    687
            if (position != 0) {
    688
                // Equivalent to contains(set, value)
    689
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    690
                // the array, and then remove the last element (sometimes called as 'swap and pop').
    691
                // This modifies the order of the array, as noted in {at}.
    692
    693
                uint256 valueIndex = position - 1;
    694
                uint256 lastIndex = set._values.length - 1;
    695
    696
                if (valueIndex != lastIndex) {
    697
                    bytes memory lastValue = set._values[lastIndex];
    698
    699
                    // Move the lastValue to the index where the value to delete is
    700
                    set._values[valueIndex] = lastValue;
    701
                    // Update the tracked position of the lastValue (that was just moved)
    702
                    set._positions[lastValue] = position;
    703
                }
    704
    705
                // Delete the slot where the moved value was stored
    706
                set._values.pop();
    707
    708
                // Delete the tracked position for the deleted slot
    709
                delete set._positions[value];
    710
    711
                return true;
    712
            } else {
    713
                return false;
    714
            }
    715
        }
    716
    717
        /**
    718
         * @dev Removes all the values from a set. O(n).
    719
         *
    720
         * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
    721
         * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
    722
         */
    723
        function clear(BytesSet storage set) internal {
    724
            uint256 len = length(set);
    725
            for (uint256 i = 0; i < len; ++i) {
    726
                delete set._positions[set._values[i]];
    727
            }
    728
            Arrays.unsafeSetLength(set._values, 0);
    729
        }
    730
    731
        /**
    732
         * @dev Returns true if the value is in the set. O(1).
    733
         */
    734
        function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
    735
            return set._positions[value] != 0;
    736
        }
    737
    738
        /**
    739
         * @dev Returns the number of values on the set. O(1).
    740
         */
    741
        function length(BytesSet storage set) internal view returns (uint256) {
    742
            return set._values.length;
    743
        }
    744
    745
        /**
    746
         * @dev Returns the value stored at position `index` in the set. O(1).
    747
         *
    748
         * Note that there are no guarantees on the ordering of values inside the
    749
         * array, and it may change when more values are added or removed.
    750
         *
    751
         * Requirements:
    752
         *
    753
         * - `index` must be strictly less than {length}.
    754
         */
    755
        function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
    756
            return set._values[index];
    757
        }
    758
    759
        /**
    760
         * @dev Return the entire set in an array
    761
         *
    762
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    763
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    764
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    765
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    766
         */
    767
        function values(BytesSet storage set) internal view returns (bytes[] memory) {
    768
            return set._values;
    769
        }
    770
    771
        /**
    772
         * @dev Return a slice of the set in an array
    773
         *
    774
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    775
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    776
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    777
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    778
         */
    779
        function values(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) {
    780
            unchecked {
    781
                end = Math.min(end, length(set));
    782
                start = Math.min(start, end);
    783
    784
                uint256 len = end - start;
    785
                bytes[] memory result = new bytes[](len);
    786
                for (uint256 i = 0; i < len; ++i) {
    787
                    result[i] = Arrays.unsafeAccess(set._values, start + i).value;
    788
                }
    789
                return result;
    790
            }
    791
        }
    792
    }
    87% lib/v2-core/src/hooks/BaseHook.sol
    Lines covered: 89 / 102 (87%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // External
    5
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    6
    7
    // Superform
    8
    import { HookDataDecoder } from "../libraries/HookDataDecoder.sol";
    9
    import { ISuperHook, ISuperHookSetter, ISuperHookResult, ISuperHookInspector } from "../interfaces/ISuperHook.sol";
    10
    11
    /// @title BaseHook
    12
    /// @author Superform Labs
    13
    /// @notice Base implementation for all hooks in the Superform system
    14
    /// @dev Provides core security validation and execution flow management for hooks
    15
    ///      All specialized hooks should inherit from this base contract
    16
    ///      Implements the ISuperHook interface defined lifecycle methods
    17
    ///      Uses a transient storage pattern for stateful execution context
    18
    abstract contract BaseHook is ISuperHook, ISuperHookSetter, ISuperHookResult, ISuperHookInspector {
    19
        using HookDataDecoder for bytes;
    20
    21
        /*//////////////////////////////////////////////////////////////
    22
                                     STORAGE
    23
        //////////////////////////////////////////////////////////////*/
    24
    25
        /// @notice The number of shares used by this hook's operation
    26
        /// @dev Used for accounting and tracking consumption of position shares
    27 0
        uint256 public transient usedShares;
    28
    29
        /// @notice The special token address (if any) associated with this hook's operation
    30
        /// @dev May be used to track token addresses for various operations
    31 0
        address public transient spToken;
    32
    33
        /// @notice The primary asset address this hook operates on
    34
        /// @dev Typically the base token or asset being processed
    35 0
        address public transient asset;
    36
    37
        /// @notice Execution nonce for creating unique contexts
    38 214×
        uint256 public transient executionNonce;
    39
    40
        /// @notice Last execution context caller
    41 0
        address public transient lastCaller;
    42
    43
        // Storage offsets for different state variables
    44 15×
        uint256 private constant OUT_AMOUNT_OFFSET = 1;
    45 22×
        uint256 private constant PRE_EXECUTE_MUTEX_OFFSET = 2;
    46 12×
        uint256 private constant POST_EXECUTE_MUTEX_OFFSET = 3;
    47
    48
        /// @notice Base storage key for hook execution state
    49 11×
        bytes32 private constant HOOK_EXECUTION_STORAGE = keccak256("hook.execution.state");
    50
    51
        /// @notice Storage key for account context mapping
    52 23×
        bytes32 private constant ACCOUNT_CONTEXT_STORAGE = keccak256("hook.account.context");
    53
    54
        /// @notice The specific subtype identifier for this hook
    55
        /// @dev Used to identify specialized hook types beyond the basic HookType enum
    56 0
        bytes32 public immutable SUB_TYPE;
    57
    58
        /// @notice The type of hook (NONACCOUNTING, INFLOW, OUTFLOW)
    59
        /// @dev Determines how the hook impacts accounting in the system
    60 102×
        ISuperHook.HookType public hookType;
    61
    62
        /*//////////////////////////////////////////////////////////////
    63
                                     ERRORS
    64
        //////////////////////////////////////////////////////////////*/
    65
        /// @notice Thrown when a caller attempts to execute hook methods without proper authorization
    66
        /// @dev Used by security validation to prevent unauthorized hook execution
    67
        error NOT_AUTHORIZED();
    68
    69
        /// @notice Thrown when an amount parameter is invalid (e.g., zero or overflow)
    70
        /// @dev Used in validation checks for asset amounts and share values
    71
        error AMOUNT_NOT_VALID();
    72
    73
        /// @notice Thrown when an address parameter is invalid (e.g., zero address)
    74
        /// @dev Used in validation checks for tokens, accounts, and other addresses
    75
        error ADDRESS_NOT_VALID();
    76
    77
        /// @notice Thrown when a caller is not authorized to execute hook methods
    78
        /// @dev Used by security validation to prevent unauthorized hook execution
    79
        error UNAUTHORIZED_CALLER();
    80
    81
        /// @notice Thrown when preExecute is called more than once
    82
        /// @dev Used to prevent reentrancy attacks and ensure proper execution flow
    83
        error PRE_EXECUTE_ALREADY_CALLED();
    84
    85
        /// @notice Thrown when postExecute is called more than once
    86
        /// @dev Used to prevent reentrancy attacks and ensure proper execution flow
    87
        error POST_EXECUTE_ALREADY_CALLED();
    88
    89
        /// @notice Thrown when a hook execution is incomplete
    90
        /// @dev Used to prevent incomplete hook execution
    91
        error INCOMPLETE_HOOK_EXECUTION();
    92
    93
        /// @notice Thrown when trying to set outAmount after preExecute or postExecute
    94
        /// @dev Used to prevent setting outAmount after preExecute or postExecute
    95
        error CANNOT_SET_OUT_AMOUNT();
    96
    97
        /// @notice Initializes the hook with its type and subtype
    98
        /// @dev Sets immutable parameters that define the hook's behavior
    99
        /// @param hookType_ The type classification for this hook (NONACCOUNTING, INFLOW, OUTFLOW)
    100
        /// @param subType_ The specific subtype identifier for specialized hook functionality
    101
        constructor(ISuperHook.HookType hookType_, bytes32 subType_) {
    102 100×
            hookType = hookType_;
    103 36×
            SUB_TYPE = subType_;
    104
        }
    105
    106
        modifier onlyLastCaller() {
    107 48×
            if (msg.sender != lastCaller) revert UNAUTHORIZED_CALLER();
    108
            _;
    109
        }
    110
    111
        /*//////////////////////////////////////////////////////////////
    112
                              EXECUTION SECURITY
    113
        //////////////////////////////////////////////////////////////*/
    114
        /// @inheritdoc ISuperHook
    115 299×
        function setExecutionContext(address caller) external {
    116 115×
            _createExecutionContext(caller);
    117 230×
            lastCaller = msg.sender;
    118
        }
    119
    120
        /// @dev Standard build pattern - MUST include preExecute first, postExecute last
    121
        /// @inheritdoc ISuperHook
    122 418×
        function build(
    123
            address prevHook,
    124
            address account,
    125
            bytes calldata hookData
    126
        )
    127
            external
    128
            view
    129
            virtual
    130 23×
            returns (Execution[] memory executions)
    131 11×
        {
    132
            // Get hook-specific executions
    133 217×
            Execution[] memory hookExecutions = _buildHookExecutions(prevHook, account, hookData);
    134
    135
            // Always include pre + hook + post
    136 627×
            executions = new Execution[](hookExecutions.length + 2);
    137
    138
            // FIRST: preExecute
    139 429×
            executions[0] = Execution({
    140 11×
                target: address(this),
    141 11×
                value: 0,
    142 572×
                callData: abi.encodeCall(this.preExecute, (prevHook, account, hookData))
    143
            });
    144
    145
            // MIDDLE: hook-specific operations
    146 165×
            for (uint256 i = 0; i < hookExecutions.length; i++) {
    147 451×
                executions[i + 1] = hookExecutions[i];
    148
            }
    149
    150
            // LAST: postExecute
    151 506×
            executions[executions.length - 1] = Execution({
    152 11×
                target: address(this),
    153 11×
                value: 0,
    154 572×
                callData: abi.encodeCall(this.postExecute, (prevHook, account, hookData))
    155
            });
    156
        }
    157
    158
        /// @inheritdoc ISuperHook
    159 121×
        function preExecute(address prevHook, address account, bytes calldata data) external {
    160 77×
            if (msg.sender != account) revert UNAUTHORIZED_CALLER();
    161 88×
            uint256 context = _getCurrentExecutionContext(account);
    162 99×
            if (_getPreExecuteMutex(context)) revert PRE_EXECUTE_ALREADY_CALLED();
    163 64×
            _setPreExecuteMutex(context, true);
    164 63×
            _preExecute(prevHook, account, data);
    165
        }
    166
    167
        /// @inheritdoc ISuperHook
    168 178×
        function postExecute(address prevHook, address account, bytes calldata data) external {
    169 42×
            if (msg.sender != account) revert UNAUTHORIZED_CALLER();
    170 48×
            uint256 context = _getCurrentExecutionContext(account);
    171 54×
            if (_getPostExecuteMutex(context)) revert POST_EXECUTE_ALREADY_CALLED();
    172 37×
            _setPostExecuteMutex(context, true);
    173 44×
            _postExecute(prevHook, account, data);
    174
        }
    175
    176
        /// @inheritdoc ISuperHookSetter
    177 27×
        function setOutAmount(uint256 _outAmount, address caller) external {
    178 0
            uint256 context = _getCurrentExecutionContext(caller);
    179 0
            if (_getPreExecuteMutex(context) || _getPostExecuteMutex(context)) {
    180 0
                revert CANNOT_SET_OUT_AMOUNT();
    181
            }
    182
    183 27×
            bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET);
    184 18×
            assembly {
    185 27×
                tstore(key, _outAmount)
    186
            }
    187
        }
    188
    189 84×
        function getOutAmount(address caller) public view returns (uint256) {
    190 80×
            return _getOutAmount(_getCurrentExecutionContext(caller));
    191
        }
    192
    193
        /// @inheritdoc ISuperHook
    194 84×
        function resetExecutionState(address caller) external onlyLastCaller {
    195 48×
            uint256 context = _getCurrentExecutionContext(caller);
    196 126×
            if (!_getPreExecuteMutex(context) || !_getPostExecuteMutex(context)) {
    197 0
                revert INCOMPLETE_HOOK_EXECUTION();
    198
            }
    199
    200 30×
            _clearExecutionState(context);
    201
        }
    202
    203
        /*//////////////////////////////////////////////////////////////
    204
                                     VIEW METHODS
    205
        //////////////////////////////////////////////////////////////*/
    206
        /// @inheritdoc ISuperHook
    207 0
        function subtype() external view returns (bytes32) {
    208 0
            return SUB_TYPE;
    209
        }
    210
    211
        /// @inheritdoc ISuperHookInspector
    212 0
        function inspect(bytes calldata) external view virtual returns (bytes memory) { }
    213
    214
        /*//////////////////////////////////////////////////////////////
    215
                                     INTERNAL METHODS
    216
        //////////////////////////////////////////////////////////////*/
    217
        /// @notice Internal implementation of build
    218
        /// @dev Abstract function to be implemented by derived hooks
    219
        ///      Called during build to generate hook-specific execution sequences
    220
        /// @param prevHook The previous hook in the chain, or address(0) if first hook
    221
        /// @param account The account that operations will be performed for
    222
        /// @param data Hook-specific parameters and configuration data
    223
        /// @return executions Array of execution objects containing target, value, and callData
    224
        function _buildHookExecutions(
    225
            address prevHook,
    226
            address account,
    227
            bytes calldata data
    228
        )
    229
            internal
    230
            view
    231
            virtual
    232
            returns (Execution[] memory executions);
    233
    234
        /// @notice Internal implementation of preExecute
    235
        /// @dev Abstract function to be implemented by derived hooks
    236
        ///      Called before execution to validate inputs and prepare the hook's state
    237
        ///      Typically sets up the hook context by parsing parameters from data
    238
        ///      May check balances, permissions, or other preconditions
    239
        /// @param prevHook The previous hook in the chain, or address(0) if first hook
    240
        /// @param account The account that operations will be performed for
    241
        /// @param data Hook-specific parameters and configuration data
    242 0
        function _preExecute(address prevHook, address account, bytes calldata data) internal virtual { }
    243
    244
        /// @notice Internal implementation of postExecute
    245
        /// @dev Abstract function to be implemented by derived hooks
    246
        ///      Called after execution to finalize the hook's state and set output values
    247
        ///      Typically calculates final results and sets outAmount, usedShares, etc.
    248
        ///      May perform additional validations on execution results
    249
        /// @param prevHook The previous hook in the chain, or address(0) if first hook
    250
        /// @param account The account operations were performed for
    251
        /// @param data Hook-specific parameters and configuration data
    252
        function _postExecute(address prevHook, address account, bytes calldata data) internal virtual { }
    253
    254
        /// @notice Decodes a boolean value from a byte array at the specified offset
    255
        /// @dev Helper function for extracting boolean values from packed data
    256
        ///      Used when parsing hook-specific data parameters
    257
        /// @param data The byte array containing the encoded data
    258
        /// @param offset The position in the array to read from
    259
        /// @return The decoded boolean value (true if byte is non-zero)
    260 114×
        function _decodeBool(bytes memory data, uint256 offset) internal pure returns (bool) {
    261 283×
            return data[offset] != 0;
    262
        }
    263
    264
        /// @notice Replaces an amount value within a byte array at the specified offset
    265
        /// @dev Used to modify hook calldata for amount adjustments without full re-encoding
    266
        ///      Particularly useful for modifying amounts in multi-hook execution chains
    267
        ///      Directly modifies the bytes in-place for gas efficiency
    268
        /// @param data The original byte array containing encoded calldata
    269
        /// @param amount The new amount value to insert
    270
        /// @param offset The position in the array where the amount starts
    271
        /// @return The modified byte array with the replaced amount
    272 18×
        function _replaceCalldataAmount(
    273
            bytes memory data,
    274
            uint256 amount,
    275
            uint256 offset
    276
        )
    277
            internal
    278
            pure
    279
            returns (bytes memory)
    280
        {
    281 138×
            bytes memory newAmountEncoded = abi.encodePacked(amount);
    282 78×
            for (uint256 i; i < 32; ++i) {
    283 240×
                data[offset + i] = newAmountEncoded[i];
    284
            }
    285 12×
            return data;
    286
        }
    287
    288 138×
        function _makeAccountContextKey(address account) private pure returns (bytes32) {
    289 690×
            return keccak256(abi.encodePacked(ACCOUNT_CONTEXT_STORAGE, account));
    290
        }
    291
    292 115×
        function _createExecutionContext(address caller) private returns (uint256) {
    293
            // Always increment nonce for new execution context
    294 322×
            executionNonce++;
    295 184×
            bytes32 key = _makeAccountContextKey(caller);
    296
    297
            // Store this context for the current caller
    298 69×
            uint256 currentNonce = executionNonce; // Load into local variable for assembly
    299
            assembly {
    300 23×
                tstore(key, currentNonce)
    301
            }
    302
    303 69×
            return executionNonce;
    304
        }
    305
    306 44×
        function _getCurrentExecutionContext(address caller) private view returns (uint256 context) {
    307 66×
            bytes32 key = _makeAccountContextKey(caller);
    308
            assembly {
    309 22×
                context := tload(key)
    310
            }
    311
        }
    312
    313 22×
        function _makeKey(uint256 context, uint256 offset) private pure returns (bytes32) {
    314 275×
            return keccak256(abi.encodePacked(HOOK_EXECUTION_STORAGE, context, offset));
    315
        }
    316
    317 12×
        function _getOutAmount(uint256 context) private view returns (uint256 value) {
    318 30×
            bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET);
    319
            assembly {
    320
                value := tload(key)
    321
            }
    322
        }
    323
    324
        function _setOutAmount(uint256 value, address caller) internal {
    325 72×
            uint256 context = _getCurrentExecutionContext(caller);
    326 45×
            bytes32 key = _makeKey(context, OUT_AMOUNT_OFFSET);
    327
            assembly {
    328
                tstore(key, value)
    329
            }
    330
        }
    331
    332 22×
        function _getPreExecuteMutex(uint256 context) private view returns (bool value) {
    333 33×
            bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET);
    334
            assembly {
    335
                value := tload(key)
    336
            }
    337
        }
    338
    339 11×
        function _setPreExecuteMutex(uint256 context, bool value) private {
    340 55×
            bytes32 key = _makeKey(context, PRE_EXECUTE_MUTEX_OFFSET);
    341
            assembly {
    342
                tstore(key, value)
    343
            }
    344
        }
    345
    346 12×
        function _getPostExecuteMutex(uint256 context) private view returns (bool value) {
    347 30×
            bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET);
    348
            assembly {
    349
                value := tload(key)
    350
            }
    351
        }
    352
    353 39×
        function _setPostExecuteMutex(uint256 context, bool value) private {
    354 63×
            bytes32 key = _makeKey(context, POST_EXECUTE_MUTEX_OFFSET);
    355 11×
            assembly {
    356 33×
                tstore(key, value)
    357
            }
    358
        }
    359
    360 36×
        function _clearExecutionState(uint256 context) private {
    361 36×
            _setPreExecuteMutex(context, false);
    362 45×
            _setPostExecuteMutex(context, false);
    363
        }
    364
    }
    95% lib/v2-core/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol
    Lines covered: 39 / 41 (95%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    8
    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    13
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    14
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    15
    import {
    16
        ISuperHookResult,
    17
        ISuperHookInflowOutflow,
    18
        ISuperHookContextAware,
    19
        ISuperHookInspector
    20
    } from "../../../interfaces/ISuperHook.sol";
    21
    22
    /// @title ApproveAndDeposit4626VaultHook
    23
    /// @author Superform Labs
    24
    /// @notice This hook does not support tokens reverting on 0 approval
    25
    /// @dev data has the following structure
    26
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    27
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    28
    /// @notice         address token = BytesLib.toAddress(data, 52);
    29
    /// @notice         uint256 amount = BytesLib.toUint256(data, 72);
    30
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 104);
    31 413×
    contract ApproveAndDeposit4626VaultHook is
    32
        BaseHook,
    33
        VaultBankLockableHook,
    34
        ISuperHookInflowOutflow,
    35
        ISuperHookContextAware
    36
    {
    37
        using HookDataDecoder for bytes;
    38
    39
        uint256 private constant AMOUNT_POSITION = 72;
    40
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104;
    41
    42
        constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { }
    43
    44
        /*//////////////////////////////////////////////////////////////
    45
                                     VIEW METHODS
    46
        //////////////////////////////////////////////////////////////*/
    47
        /// @inheritdoc BaseHook
    48
        function _buildHookExecutions(
    49
            address prevHook,
    50
            address account,
    51
            bytes calldata data
    52
        )
    53
            internal
    54
            view
    55
            override
    56
            returns (Execution[] memory executions)
    57
        {
    58 102×
            address yieldSource = data.extractYieldSource();
    59 106×
            address token = BytesLib.toAddress(data, 52);
    60 102×
            uint256 amount = _decodeAmount(data);
    61 102×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    62
    63 10×
            if (usePrevHookAmount) {
    64 55×
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    65
            }
    66
    67 24×
            if (amount == 0) revert AMOUNT_NOT_VALID();
    68 14×
            if (yieldSource == address(0) || token == address(0)) revert ADDRESS_NOT_VALID();
    69
    70 34×
            executions = new Execution[](4);
    71 16×
            executions[0] =
    72 52×
                Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
    73 20×
            executions[1] =
    74 53×
                Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) });
    75 20×
            executions[2] =
    76 53×
                Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) });
    77 20×
            executions[3] =
    78 53×
                Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
    79
        }
    80
    81
        /*//////////////////////////////////////////////////////////////
    82
                                     EXTERNAL METHODS
    83
        //////////////////////////////////////////////////////////////*/
    84
    85
        /// @inheritdoc ISuperHookInflowOutflow
    86 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    87 0
            return _decodeAmount(data);
    88
        }
    89
    90
        /// @inheritdoc ISuperHookContextAware
    91 32×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    92
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    93
        }
    94
    95
        /// @inheritdoc ISuperHookInspector
    96 50×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    97 38×
            return abi.encodePacked(
    98 96×
                data.extractYieldSource(),
    99 100×
                BytesLib.toAddress(data, 52) //token
    100
            );
    101
        }
    102
    103
        /*//////////////////////////////////////////////////////////////
    104
                                     INTERNAL METHODS
    105
        //////////////////////////////////////////////////////////////*/
    106
        function _preExecute(address, address account, bytes calldata data) internal override {
    107
            // store current balance
    108 50×
            _setOutAmount(_getBalance(account, data), account);
    109 58×
            spToken = data.extractYieldSource();
    110
        }
    111
    112
        function _postExecute(address, address account, bytes calldata data) internal override {
    113 65×
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    114
        }
    115
    116
        /*//////////////////////////////////////////////////////////////
    117
                                     PRIVATE METHODS
    118
        //////////////////////////////////////////////////////////////*/
    119
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    120
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    121
        }
    122
    123
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    124 66×
            return IERC4626(data.extractYieldSource()).balanceOf(account);
    125
        }
    126
    }
    62% lib/v2-core/src/hooks/vaults/4626/Deposit4626VaultHook.sol
    Lines covered: 20 / 32 (62%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    8
    9
    // Superform
    10
    import { BaseHook } from "../../BaseHook.sol";
    11
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    12
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    13
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    14
    import {
    15
        ISuperHookResult,
    16
        ISuperHookInflowOutflow,
    17
        ISuperHookContextAware,
    18
        ISuperHookInspector
    19
    } from "../../../interfaces/ISuperHook.sol";
    20
    21
    /// @title Deposit4626VaultHook
    22
    /// @author Superform Labs
    23
    /// @dev data has the following structure
    24
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    25
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    26
    /// @notice         uint256 amount = BytesLib.toUint256(data, 52);
    27
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    28 296×
    contract Deposit4626VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware {
    29
        using HookDataDecoder for bytes;
    30
    31
        uint256 private constant AMOUNT_POSITION = 52;
    32
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    33
    34
        constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { }
    35
        /*//////////////////////////////////////////////////////////////
    36
                                     VIEW METHODS
    37
        //////////////////////////////////////////////////////////////*/
    38
        /// @inheritdoc BaseHook
    39
    40
        function _buildHookExecutions(
    41
            address prevHook,
    42
            address account,
    43
            bytes calldata data
    44
        )
    45
            internal
    46
            view
    47
            override
    48
            returns (Execution[] memory executions)
    49 0
        {
    50 51×
            address yieldSource = data.extractYieldSource();
    51 51×
            uint256 amount = _decodeAmount(data);
    52 51×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    53
    54
            if (usePrevHookAmount) {
    55 55×
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    56
            }
    57
    58 18×
            if (amount == 0) revert AMOUNT_NOT_VALID();
    59 0
            if (yieldSource == address(0)) revert ADDRESS_NOT_VALID();
    60
    61 0
            executions = new Execution[](1);
    62 0
            executions[0] =
    63 0
                Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(IERC4626.deposit, (amount, account)) });
    64
        }
    65
    66
        /*//////////////////////////////////////////////////////////////
    67
                                     EXTERNAL METHODS
    68
        //////////////////////////////////////////////////////////////*/
    69
    70
        /// @inheritdoc ISuperHookInflowOutflow
    71 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    72 0
            return _decodeAmount(data);
    73
        }
    74
    75
        /// @inheritdoc ISuperHookContextAware
    76 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    77
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    78
        }
    79
    80
        /// @inheritdoc ISuperHookInspector
    81 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    82 70×
            return abi.encodePacked(data.extractYieldSource());
    83
        }
    84
    85
        /*//////////////////////////////////////////////////////////////
    86
                                     INTERNAL METHODS
    87
        //////////////////////////////////////////////////////////////*/
    88 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    89
            // store current balance
    90 0
            _setOutAmount(_getBalance(account, data), account);
    91 0
            spToken = data.extractYieldSource();
    92
        }
    93
    94 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    95 0
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    96
        }
    97
    98
        /*//////////////////////////////////////////////////////////////
    99
                                     PRIVATE METHODS
    100
        //////////////////////////////////////////////////////////////*/
    101
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    102
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    103
        }
    104
    105
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    106
            return IERC4626(data.extractYieldSource()).balanceOf(account);
    107
        }
    108
    }
    97% lib/v2-core/src/hooks/vaults/4626/Redeem4626VaultHook.sol
    Lines covered: 46 / 47 (97%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    8
    import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import {
    13
        ISuperHookResultOutflow,
    14
        ISuperHookInflowOutflow,
    15
        ISuperHookOutflow,
    16
        ISuperHookContextAware,
    17
        ISuperHookInspector
    18
    } from "../../../interfaces/ISuperHook.sol";
    19
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    20
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    21
    22
    /// @title Redeem4626VaultHook
    23
    /// @author Superform Labs
    24
    /// @dev data has the following structure
    25
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    26
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    27
    /// @notice         address owner = BytesLib.toAddress(data, 52);
    28
    /// @notice         uint256 shares = BytesLib.toUint256(data, 72);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 104);
    30 426×
    contract Redeem4626VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
    31
        using HookDataDecoder for bytes;
    32
    33
        uint256 private constant AMOUNT_POSITION = 72;
    34
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104;
    35
    36
        constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC4626) { }
    37
    38
        /*//////////////////////////////////////////////////////////////
    39
                                     VIEW METHODS
    40
        //////////////////////////////////////////////////////////////*/
    41
        /// @inheritdoc BaseHook
    42 16×
        function _buildHookExecutions(
    43
            address prevHook,
    44
            address account,
    45
            bytes calldata data
    46
        )
    47
            internal
    48
            view
    49
            override
    50
            returns (Execution[] memory executions)
    51
        {
    52 102×
            address yieldSource = data.extractYieldSource();
    53 106×
            address owner = BytesLib.toAddress(data, 52);
    54 102×
            uint256 shares = _decodeAmount(data);
    55 102×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    56
    57 10×
            if (usePrevHookAmount) {
    58 55×
                shares = ISuperHookResultOutflow(prevHook).getOutAmount(account);
    59
            }
    60
    61 38×
            if (shares == 0) revert AMOUNT_NOT_VALID();
    62 10×
            if (yieldSource == address(0)) revert ADDRESS_NOT_VALID();
    63
    64 68×
            executions = new Execution[](1);
    65 80×
            executions[0] = Execution({
    66
                target: yieldSource,
    67
                value: 0,
    68 52×
                callData: abi.encodeCall(IERC4626.redeem, (shares, account, owner))
    69
            });
    70
        }
    71
    72
        /*//////////////////////////////////////////////////////////////
    73
                                     EXTERNAL METHODS
    74
        //////////////////////////////////////////////////////////////*/
    75
    76
        /// @inheritdoc ISuperHookInflowOutflow
    77 24×
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    78
            return _decodeAmount(data);
    79
        }
    80
    81
        /// @inheritdoc ISuperHookContextAware
    82 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    83
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    84
        }
    85
    86
        /// @inheritdoc ISuperHookOutflow
    87 52×
        function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
    88 16×
            return _replaceCalldataAmount(data, amount, AMOUNT_POSITION);
    89
        }
    90
    91
        /// @inheritdoc ISuperHookInspector
    92 34×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    93 38×
            return abi.encodePacked(
    94 96×
                data.extractYieldSource(),
    95 100×
                BytesLib.toAddress(data, 52) // owner
    96
            );
    97
        }
    98
    99
        /*//////////////////////////////////////////////////////////////
    100
                                     INTERNAL METHODS
    101
        //////////////////////////////////////////////////////////////*/
    102 14×
        function _preExecute(address, address account, bytes calldata data) internal override {
    103 102×
            address yieldSource = data.extractYieldSource();
    104 136×
            asset = IERC4626(yieldSource).asset();
    105 108×
            _setOutAmount(_getBalance(account, data), account);
    106 104×
            usedShares = _getSharesBalance(account, data);
    107 22×
            spToken = yieldSource;
    108
        }
    109
    110
        function _postExecute(address, address account, bytes calldata data) internal override {
    111 65×
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    112 60×
            usedShares = usedShares - _getSharesBalance(account, data);
    113
        }
    114
    115
        /*//////////////////////////////////////////////////////////////
    116
                                     PRIVATE METHODS
    117
        //////////////////////////////////////////////////////////////*/
    118
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    119
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    120
        }
    121
    122
        function _getBalance(address account, bytes memory) private view returns (uint256) {
    123 116×
            return IERC20(asset).balanceOf(account);
    124
        }
    125
    126
        function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
    127 16×
            address yieldSource = data.extractYieldSource();
    128 16×
            address owner = BytesLib.toAddress(data, 52);
    129 10×
            if (owner == address(0)) {
    130 0
                owner = account;
    131
            }
    132 114×
            return IERC4626(yieldSource).balanceOf(owner);
    133
        }
    134
    }
    88% lib/v2-core/src/hooks/vaults/5115/ApproveAndDeposit5115VaultHook.sol
    Lines covered: 40 / 45 (88%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
    8
    import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    13
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    14
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    15
    import {
    16
        ISuperHookResult,
    17
        ISuperHookInflowOutflow,
    18
        ISuperHookContextAware,
    19
        ISuperHookInspector
    20
    } from "../../../interfaces/ISuperHook.sol";
    21
    22
    /// @title ApproveAndDeposit5115VaultHook
    23
    /// @author Superform Labs
    24
    /// @notice This hook does not support tokens reverting on 0 approval
    25
    /// @dev data has the following structure
    26
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    27
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    28
    /// @notice         address tokenIn = BytesLib.toAddress(data, 52);
    29
    /// @notice         uint256 amount = BytesLib.toUint256(data, 72);
    30
    /// @notice         uint256 minSharesOut = BytesLib.toUint256(data, 104);
    31
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 136);
    32 413×
    contract ApproveAndDeposit5115VaultHook is
    33
        BaseHook,
    34
        VaultBankLockableHook,
    35
        ISuperHookInflowOutflow,
    36
        ISuperHookContextAware
    37
    {
    38
        using HookDataDecoder for bytes;
    39
    40
        uint256 private constant AMOUNT_POSITION = 72;
    41
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136;
    42
    43
        constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC5115) { }
    44
    45
        /*//////////////////////////////////////////////////////////////
    46
                                     VIEW METHODS
    47
        //////////////////////////////////////////////////////////////*/
    48
        /// @inheritdoc BaseHook
    49
        function _buildHookExecutions(
    50
            address prevHook,
    51
            address account,
    52
            bytes calldata data
    53
        )
    54
            internal
    55
            view
    56
            override
    57
            returns (Execution[] memory executions)
    58
        {
    59 102×
            address yieldSource = data.extractYieldSource();
    60 106×
            address tokenIn = BytesLib.toAddress(data, 52);
    61 102×
            uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION);
    62 106×
            uint256 minSharesOut = BytesLib.toUint256(data, 104);
    63 102×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    64
    65 10×
            if (usePrevHookAmount) {
    66 55×
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    67
            }
    68
    69 24×
            if (amount == 0) revert AMOUNT_NOT_VALID();
    70 21×
            if (yieldSource == address(0) || account == address(0) || tokenIn == address(0)) revert ADDRESS_NOT_VALID();
    71
    72 34×
            executions = new Execution[](4);
    73 16×
            executions[0] =
    74 52×
                Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
    75 20×
            executions[1] =
    76 53×
                Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) });
    77 39×
            executions[2] = Execution({
    78
                target: yieldSource,
    79
                value: 0,
    80 36×
                callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut))
    81
            });
    82 20×
            executions[3] =
    83 53×
                Execution({ target: tokenIn, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
    84
        }
    85
    86
        /*//////////////////////////////////////////////////////////////
    87
                                     EXTERNAL METHODS
    88
        //////////////////////////////////////////////////////////////*/
    89
    90
        /// @inheritdoc ISuperHookInflowOutflow
    91 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    92 0
            return _decodeAmount(data);
    93
        }
    94
    95
        /// @inheritdoc ISuperHookContextAware
    96 32×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    97
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    98
        }
    99
    100
        /// @inheritdoc ISuperHookInspector
    101 50×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    102 38×
            return abi.encodePacked(
    103 96×
                data.extractYieldSource(),
    104 100×
                BytesLib.toAddress(data, 52) // tokenIn
    105
            );
    106
        }
    107
    108
        /*//////////////////////////////////////////////////////////////
    109
                                     INTERNAL METHODS
    110
        //////////////////////////////////////////////////////////////*/
    111
        function _preExecute(address, address account, bytes calldata data) internal override {
    112 50×
            _setOutAmount(_getBalance(account, data), account);
    113 58×
            spToken = data.extractYieldSource();
    114 60×
            asset = BytesLib.toAddress(data, 52);
    115
        }
    116
    117 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    118
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    119
        }
    120
    121
        /*//////////////////////////////////////////////////////////////
    122
                                     PRIVATE METHODS
    123
        //////////////////////////////////////////////////////////////*/
    124 0
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    125 0
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    126
        }
    127
    128
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    129 66×
            return IStandardizedYield(data.extractYieldSource()).balanceOf(account);
    130
        }
    131
    }
    46% lib/v2-core/src/hooks/vaults/5115/Deposit5115VaultHook.sol
    Lines covered: 18 / 39 (46%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol";
    8
    9
    // Superform
    10
    import { BaseHook } from "../../BaseHook.sol";
    11
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    12
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    13
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    14
    import {
    15
        ISuperHookResult,
    16
        ISuperHookInflowOutflow,
    17
        ISuperHookContextAware,
    18
        ISuperHookInspector
    19
    } from "../../../interfaces/ISuperHook.sol";
    20
    21
    /// @title Deposit5115VaultHook
    22
    /// @author Superform Labs
    23
    /// @dev data has the following structure
    24
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    25
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    26
    /// @notice         address tokenIn = BytesLib.toAddress(data, 52);
    27
    /// @notice         uint256 amount = BytesLib.toUint256(data, 72);
    28
    /// @notice         uint256 minSharesOut = BytesLib.toUint256(data, 104);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 136);
    30 296×
    contract Deposit5115VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware {
    31
        using HookDataDecoder for bytes;
    32
    33
        uint256 private constant AMOUNT_POSITION = 72;
    34
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136;
    35
    36
        constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC5115) { }
    37
    38
        /*//////////////////////////////////////////////////////////////
    39
                                     VIEW METHODS
    40
        //////////////////////////////////////////////////////////////*/
    41
        /// @inheritdoc BaseHook
    42
        function _buildHookExecutions(
    43
            address prevHook,
    44
            address account,
    45
            bytes calldata data
    46
        )
    47
            internal
    48
            view
    49
            override
    50
            returns (Execution[] memory executions)
    51 0
        {
    52 51×
            address yieldSource = data.extractYieldSource();
    53 53×
            address tokenIn = BytesLib.toAddress(data, 52);
    54 51×
            uint256 amount = BytesLib.toUint256(data, AMOUNT_POSITION);
    55 50×
            uint256 minSharesOut = BytesLib.toUint256(data, 104);
    56 0
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    57
    58 0
            if (usePrevHookAmount) {
    59 0
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    60
            }
    61
    62 0
            if (amount == 0) revert AMOUNT_NOT_VALID();
    63 0
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    64
    65 0
            executions = new Execution[](1);
    66 0
            executions[0] = Execution({
    67 0
                target: yieldSource,
    68 0
                value: tokenIn == address(0) ? amount : 0,
    69 0
                callData: abi.encodeCall(IStandardizedYield.deposit, (account, tokenIn, amount, minSharesOut))
    70
            });
    71
        }
    72
    73
        /*//////////////////////////////////////////////////////////////
    74
                                     EXTERNAL METHODS
    75
        //////////////////////////////////////////////////////////////*/
    76
    77
        /// @inheritdoc ISuperHookInflowOutflow
    78 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    79 0
            return _decodeAmount(data);
    80
        }
    81
    82
        /// @inheritdoc ISuperHookContextAware
    83 12×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    84
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    85
        }
    86
    87
        /// @inheritdoc ISuperHookInspector
    88 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    89 19×
            return abi.encodePacked(
    90 48×
                data.extractYieldSource(),
    91 50×
                BytesLib.toAddress(data, 52) // tokenIn
    92
            );
    93
        }
    94
    95
        /*//////////////////////////////////////////////////////////////
    96
                                     INTERNAL METHODS
    97
        //////////////////////////////////////////////////////////////*/
    98 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    99 0
            _setOutAmount(_getBalance(account, data), account);
    100 0
            spToken = data.extractYieldSource();
    101 0
            asset = BytesLib.toAddress(data, 52);
    102
        }
    103
    104 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    105 0
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    106
        }
    107
    108
        /*//////////////////////////////////////////////////////////////
    109
                                     PRIVATE METHODS
    110
        //////////////////////////////////////////////////////////////*/
    111 0
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    112 0
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    113
        }
    114
    115
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    116
            return IStandardizedYield(data.extractYieldSource()).balanceOf(account);
    117
        }
    118
    }
    61% lib/v2-core/src/hooks/vaults/5115/Redeem5115VaultHook.sol
    Lines covered: 27 / 44 (61%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    7
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    8
    import { IStandardizedYield } from "../../../vendor/pendle/IStandardizedYield.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import {
    13
        ISuperHookResultOutflow,
    14
        ISuperHookInflowOutflow,
    15
        ISuperHookOutflow,
    16
        ISuperHookContextAware,
    17
        ISuperHookInspector
    18
    } from "../../../interfaces/ISuperHook.sol";
    19
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    20
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    21
    22
    /// @title Redeem5115VaultHook
    23
    /// @author Superform Labs
    24
    /// @dev data has the following structure
    25
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    26
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    27
    /// @notice         address tokenOut = BytesLib.toAddress(data, 52);
    28
    /// @notice         uint256 shares = BytesLib.toUint256(data, 72);
    29
    /// @notice         uint256 minTokenOut = BytesLib.toUint256(data, 104);
    30
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 136);
    31 408×
    contract Redeem5115VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
    32
        using HookDataDecoder for bytes;
    33
    34
        uint256 private constant AMOUNT_POSITION = 72;
    35
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 136;
    36
    37
        constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC5115) { }
    38
    39
        /*//////////////////////////////////////////////////////////////
    40
                                     VIEW METHODS
    41
        //////////////////////////////////////////////////////////////*/
    42
        /// @inheritdoc BaseHook
    43
        function _buildHookExecutions(
    44
            address prevHook,
    45
            address account,
    46
            bytes calldata data
    47
        )
    48
            internal
    49
            view
    50
            override
    51
            returns (Execution[] memory executions)
    52 0
        {
    53 102×
            address yieldSource = data.extractYieldSource();
    54 106×
            address tokenOut = BytesLib.toAddress(data, 52);
    55 102×
            uint256 shares = _decodeAmount(data);
    56 103×
            uint256 minTokenOut = BytesLib.toUint256(data, 104);
    57 51×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    58
    59
            if (usePrevHookAmount) {
    60 55×
                shares = ISuperHookResultOutflow(prevHook).getOutAmount(account);
    61
            }
    62
    63 0
            if (shares == 0) revert AMOUNT_NOT_VALID();
    64 0
            if (yieldSource == address(0) || tokenOut == address(0)) revert ADDRESS_NOT_VALID();
    65
    66 0
            executions = new Execution[](1);
    67 0
            executions[0] = Execution({
    68
                target: yieldSource,
    69
                value: 0,
    70 0
                callData: abi.encodeCall(IStandardizedYield.redeem, (account, shares, tokenOut, minTokenOut, false))
    71
            });
    72
        }
    73
    74
        /*//////////////////////////////////////////////////////////////
    75
                                     EXTERNAL METHODS
    76
        //////////////////////////////////////////////////////////////*/
    77
    78
        /// @inheritdoc ISuperHookInflowOutflow
    79 24×
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    80
            return _decodeAmount(data);
    81
        }
    82
    83
        /// @inheritdoc ISuperHookContextAware
    84 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    85
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    86
        }
    87
    88
        /// @inheritdoc ISuperHookOutflow
    89 52×
        function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
    90 16×
            return _replaceCalldataAmount(data, amount, AMOUNT_POSITION);
    91
        }
    92
    93
        /// @inheritdoc ISuperHookInspector
    94 34×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    95 38×
            return abi.encodePacked(
    96 96×
                data.extractYieldSource(),
    97 100×
                BytesLib.toAddress(data, 52) // tokenOut
    98
            );
    99
        }
    100
    101
        /*//////////////////////////////////////////////////////////////
    102
                                     INTERNAL METHODS
    103
        //////////////////////////////////////////////////////////////*/
    104 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    105 0
            asset = BytesLib.toAddress(data, 52); // tokenOut from data
    106 0
            _setOutAmount(_getBalance(account, data), account);
    107 0
            usedShares = _getSharesBalance(account, data);
    108 0
            spToken = data.extractYieldSource();
    109
        }
    110
    111 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    112 0
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    113 0
            usedShares = usedShares - _getSharesBalance(account, data);
    114
        }
    115
    116
        /*//////////////////////////////////////////////////////////////
    117
                                     PRIVATE METHODS
    118
        //////////////////////////////////////////////////////////////*/
    119
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    120
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    121
        }
    122
    123 0
        function _getBalance(address account, bytes memory) private view returns (uint256) {
    124 0
            return IERC20(asset).balanceOf(account);
    125
        }
    126
    127
        function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
    128 0
            address yieldSource = data.extractYieldSource();
    129
            return IStandardizedYield(yieldSource).balanceOf(account);
    130
        }
    131
    }
    52% lib/v2-core/src/hooks/vaults/7540/ApproveAndRequestDeposit7540VaultHook.sol
    Lines covered: 22 / 42 (52%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
    7
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    8
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    9
    10
    // Superform
    11
    import {
    12
        ISuperHookResult,
    13
        ISuperHookInflowOutflow,
    14
        ISuperHookContextAware,
    15
        ISuperHookInspector
    16
    } from "../../../interfaces/ISuperHook.sol";
    17
    import { BaseHook } from "../../BaseHook.sol";
    18
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    19
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    20
    21
    /// @title ApproveAndRequestDeposit7540VaultHook
    22
    /// @author Superform Labs
    23
    /// @notice This hook does not support tokens reverting on 0 approval
    24
    /// @dev data has the following structure
    25
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    26
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    27
    /// @notice         address token = BytesLib.toAddress(data, 52);
    28
    /// @notice         uint256 amount = BytesLib.toUint256(data, 72);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 104);
    30 393×
    contract ApproveAndRequestDeposit7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookContextAware {
    31
        using HookDataDecoder for bytes;
    32
    33
        uint256 private constant AMOUNT_POSITION = 72;
    34
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 104;
    35
    36
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { }
    37
    38
        /*//////////////////////////////////////////////////////////////
    39
                                     VIEW METHODS
    40
        //////////////////////////////////////////////////////////////*/
    41
        /// @inheritdoc BaseHook
    42
        function _buildHookExecutions(
    43
            address prevHook,
    44
            address account,
    45
            bytes calldata data
    46
        )
    47
            internal
    48
            view
    49
            override
    50
            returns (Execution[] memory executions)
    51 0
        {
    52 102×
            address yieldSource = data.extractYieldSource();
    53 106×
            address token = BytesLib.toAddress(data, 52);
    54 102×
            uint256 amount = _decodeAmount(data);
    55 102×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    56
    57
            if (usePrevHookAmount) {
    58 110×
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    59
            }
    60
    61 0
            if (amount == 0) revert AMOUNT_NOT_VALID();
    62 0
            if (yieldSource == address(0) || account == address(0) || token == address(0)) revert ADDRESS_NOT_VALID();
    63
    64 0
            executions = new Execution[](4);
    65 0
            executions[0] =
    66 0
                Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
    67 0
            executions[1] =
    68 0
                Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, amount)) });
    69 0
            executions[2] = Execution({
    70 0
                target: yieldSource,
    71 0
                value: 0,
    72 0
                callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account))
    73
            });
    74 0
            executions[3] =
    75 0
                Execution({ target: token, value: 0, callData: abi.encodeCall(IERC20.approve, (yieldSource, 0)) });
    76
        }
    77
    78
        /*//////////////////////////////////////////////////////////////
    79
                                     EXTERNAL METHODS
    80
        //////////////////////////////////////////////////////////////*/
    81
    82
        /// @inheritdoc ISuperHookInflowOutflow
    83 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    84 0
            return _decodeAmount(data);
    85
        }
    86
    87
        /// @inheritdoc ISuperHookContextAware
    88 32×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    89
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    90
        }
    91
    92
        /// @inheritdoc ISuperHookInspector
    93 50×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    94 38×
            return abi.encodePacked(
    95 96×
                data.extractYieldSource(),
    96 100×
                BytesLib.toAddress(data, 52) //token
    97
            );
    98
        }
    99
    100
        /*//////////////////////////////////////////////////////////////    
    101
                                     INTERNAL METHODS
    102
        //////////////////////////////////////////////////////////////*/
    103 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    104 0
            _setOutAmount(_getBalance(account, data), account);
    105
        }
    106
    107 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    108 0
            _setOutAmount(getOutAmount(account) - _getBalance(account, data), account);
    109
        }
    110
    111
        /*//////////////////////////////////////////////////////////////
    112
                                     PRIVATE METHODS
    113
        //////////////////////////////////////////////////////////////*/
    114
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    115
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    116
        }
    117
    118
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    119
            return IERC20(IERC7540(data.extractYieldSource()).asset()).balanceOf(account);
    120
        }
    121
    }
    100% lib/v2-core/src/hooks/vaults/7540/CancelDepositRequest7540Hook.sol
    Lines covered: 13 / 13 (100%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    6
    import { IERC7540CancelDeposit } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
    7
    8
    // Superform
    9
    import { BaseHook } from "../../BaseHook.sol";
    10
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    11
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    12
    import { ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
    13
    14
    /// @title CancelDepositRequest7540Hook
    15
    /// @author Superform Labs
    16
    /// @dev data has the following structure
    17
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    18
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    19 269×
    contract CancelDepositRequest7540Hook is BaseHook {
    20
        using HookDataDecoder for bytes;
    21
    22
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_DEPOSIT_REQUEST) { }
    23
    24
        /*//////////////////////////////////////////////////////////////
    25
                                     VIEW METHODS
    26
        //////////////////////////////////////////////////////////////*/
    27
        /// @inheritdoc BaseHook
    28
        function _buildHookExecutions(
    29
            address,
    30
            address account,
    31
            bytes calldata data
    32
        )
    33
            internal
    34
            pure
    35
            override
    36
            returns (Execution[] memory executions)
    37
        {
    38 50×
            address yieldSource = data.extractYieldSource();
    39
    40
            if (yieldSource == address(0)) revert ADDRESS_NOT_VALID();
    41
    42 35×
            executions = new Execution[](1);
    43 25×
            executions[0] = Execution({
    44
                target: yieldSource,
    45
                value: 0,
    46 32×
                callData: abi.encodeCall(IERC7540CancelDeposit.cancelDepositRequest, (0, account))
    47
            });
    48
        }
    49
    50
        /// @inheritdoc ISuperHookInspector
    51 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    52 70×
            return abi.encodePacked(data.extractYieldSource());
    53
        }
    54
    }
    100% lib/v2-core/src/hooks/vaults/7540/CancelRedeemRequest7540Hook.sol
    Lines covered: 13 / 13 (100%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    6
    import { IERC7540CancelRedeem } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
    7
    8
    // Superform
    9
    import { BaseHook } from "../../BaseHook.sol";
    10
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    11
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    12
    import { ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
    13
    14
    /// @title CancelRedeemRequest7540Hook
    15
    /// @author Superform Labs
    16
    /// @dev data has the following structure
    17
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    18
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    19 269×
    contract CancelRedeemRequest7540Hook is BaseHook {
    20
        using HookDataDecoder for bytes;
    21
    22
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_REDEEM_REQUEST) { }
    23
    24
        /*//////////////////////////////////////////////////////////////
    25
                                     VIEW METHODS
    26
        //////////////////////////////////////////////////////////////*/
    27
        /// @inheritdoc BaseHook
    28
        function _buildHookExecutions(
    29
            address,
    30
            address account,
    31
            bytes calldata data
    32
        )
    33
            internal
    34
            pure
    35
            override
    36
            returns (Execution[] memory executions)
    37
        {
    38 50×
            address yieldSource = data.extractYieldSource();
    39
    40
            if (yieldSource == address(0)) revert ADDRESS_NOT_VALID();
    41
    42 35×
            executions = new Execution[](1);
    43 25×
            executions[0] = Execution({
    44
                target: yieldSource,
    45
                value: 0,
    46 32×
                callData: abi.encodeCall(IERC7540CancelRedeem.cancelRedeemRequest, (0, account))
    47
            });
    48
        }
    49
    50
        /// @inheritdoc ISuperHookInspector
    51 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    52 70×
            return abi.encodePacked(data.extractYieldSource());
    53
        }
    54
    }
    93% lib/v2-core/src/hooks/vaults/7540/ClaimCancelDepositRequest7540Hook.sol
    Lines covered: 27 / 29 (93%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC7540CancelDeposit } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
    8
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    9
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    10
    11
    // Superform
    12
    import { BaseHook } from "../../BaseHook.sol";
    13
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    14
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    15
    import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
    16
    17
    /// @title ClaimCancelDepositRequest7540Hook
    18
    /// @author Superform Labs
    19
    /// @dev data has the following structure
    20
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    21
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    22
    /// @notice         address receiver = BytesLib.toAddress(data, 52);
    23 261×
    contract ClaimCancelDepositRequest7540Hook is BaseHook, ISuperHookAsyncCancelations {
    24
        using HookDataDecoder for bytes;
    25
    26
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CLAIM_CANCEL_DEPOSIT_REQUEST) { }
    27
    28
        /*//////////////////////////////////////////////////////////////
    29
                                     VIEW METHODS
    30
        //////////////////////////////////////////////////////////////*/
    31
        /// @inheritdoc BaseHook
    32
        function _buildHookExecutions(
    33
            address,
    34
            address account,
    35
            bytes calldata data
    36
        )
    37
            internal
    38
            pure
    39
            override
    40
            returns (Execution[] memory executions)
    41
        {
    42 51×
            address yieldSource = data.extractYieldSource();
    43 52×
            address receiver = BytesLib.toAddress(data, 52);
    44
    45 14×
            if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID();
    46
    47 35×
            executions = new Execution[](1);
    48 35×
            executions[0] = Execution({
    49
                target: yieldSource,
    50
                value: 0,
    51 34×
                callData: abi.encodeCall(IERC7540CancelDeposit.claimCancelDepositRequest, (0, receiver, account))
    52
            });
    53
        }
    54
    55
        /*//////////////////////////////////////////////////////////////
    56
                                     EXTERNAL METHODS
    57
        //////////////////////////////////////////////////////////////*/
    58
    59
        /// @inheritdoc ISuperHookAsyncCancelations
    60 0
        function isAsyncCancelHook() external pure returns (CancelationType) {
    61 0
            return CancelationType.INFLOW;
    62
        }
    63
    64
        /// @inheritdoc ISuperHookInspector
    65 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    66 19×
            return abi.encodePacked(
    67 48×
                data.extractYieldSource(),
    68 50×
                BytesLib.toAddress(data, 52) //receiver
    69
            );
    70
        }
    71
    72
        /*//////////////////////////////////////////////////////////////
    73
                                     INTERNAL METHODS
    74
        //////////////////////////////////////////////////////////////*/
    75
        function _preExecute(address, address account, bytes calldata data) internal override {
    76 51×
            address yieldSource = data.extractYieldSource();
    77 53×
            address receiver = BytesLib.toAddress(data, 52);
    78 75×
            asset = IERC7540(yieldSource).asset();
    79
            // store current balance
    80 54×
            _setOutAmount(_getBalance(receiver, data), account);
    81
        }
    82
    83
        function _postExecute(address, address account, bytes calldata data) internal override {
    84 53×
            address receiver = BytesLib.toAddress(data, 52);
    85
    86 64×
            _setOutAmount(_getBalance(receiver, data) - getOutAmount(account), account);
    87
        }
    88
    89
        /*//////////////////////////////////////////////////////////////
    90
                                     PRIVATE METHODS
    91
        //////////////////////////////////////////////////////////////*/
    92
    93
        function _getBalance(address account, bytes memory) private view returns (uint256) {
    94 60×
            return IERC20(asset).balanceOf(account);
    95
        }
    96
    }
    92% lib/v2-core/src/hooks/vaults/7540/ClaimCancelRedeemRequest7540Hook.sol
    Lines covered: 25 / 27 (92%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    8
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    9
    import { IERC7540CancelRedeem } from "../../../vendor/standards/ERC7540/IERC7540Vault.sol";
    10
    11
    // Superform
    12
    import { BaseHook } from "../../BaseHook.sol";
    13
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    14
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    15
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    16
    import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
    17
    18
    /// @title ClaimCancelRedeemRequest7540Hook
    19
    /// @author Superform Labs
    20
    /// @dev data has the following structure
    21
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    22
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    23
    /// @notice         address receiver = BytesLib.toAddress(data, 52);
    24 276×
    contract ClaimCancelRedeemRequest7540Hook is BaseHook, VaultBankLockableHook, ISuperHookAsyncCancelations {
    25
        using HookDataDecoder for bytes;
    26
    27
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CLAIM_CANCEL_REDEEM_REQUEST) { }
    28
    29
        /*//////////////////////////////////////////////////////////////
    30
                                     VIEW METHODS
    31
        //////////////////////////////////////////////////////////////*/
    32
        /// @inheritdoc BaseHook
    33
        function _buildHookExecutions(
    34
            address,
    35
            address account,
    36
            bytes calldata data
    37
        )
    38
            internal
    39
            pure
    40
            override
    41
            returns (Execution[] memory executions)
    42
        {
    43 51×
            address yieldSource = data.extractYieldSource();
    44 52×
            address receiver = BytesLib.toAddress(data, 52);
    45
    46 14×
            if (yieldSource == address(0) || receiver == address(0)) revert ADDRESS_NOT_VALID();
    47
    48 35×
            executions = new Execution[](1);
    49 35×
            executions[0] = Execution({
    50
                target: yieldSource,
    51
                value: 0,
    52 34×
                callData: abi.encodeCall(IERC7540CancelRedeem.claimCancelRedeemRequest, (0, receiver, account))
    53
            });
    54
        }
    55
    56
        /*//////////////////////////////////////////////////////////////
    57
                                     EXTERNAL METHODS
    58
        //////////////////////////////////////////////////////////////*/
    59
    60
        /// @inheritdoc ISuperHookAsyncCancelations
    61 0
        function isAsyncCancelHook() external pure returns (CancelationType) {
    62 0
            return CancelationType.OUTFLOW;
    63
        }
    64
    65
        /// @inheritdoc ISuperHookInspector
    66 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    67 19×
            return abi.encodePacked(
    68 48×
                data.extractYieldSource(),
    69 50×
                BytesLib.toAddress(data, 52) //receiver
    70
            );
    71
        }
    72
    73
        /*//////////////////////////////////////////////////////////////
    74
                                     INTERNAL METHODS
    75
        //////////////////////////////////////////////////////////////*/
    76
        function _preExecute(address, address account, bytes calldata data) internal override {
    77 54×
            _setOutAmount(_getBalance(account, data), account);
    78 115×
            spToken = IERC7540(data.extractYieldSource()).share();
    79
        }
    80
    81
        function _postExecute(address, address account, bytes calldata data) internal override {
    82 53×
            address receiver = BytesLib.toAddress(data, 52);
    83 64×
            _setOutAmount(_getBalance(receiver, data) - getOutAmount(account), account);
    84
        }
    85
    86
        /*//////////////////////////////////////////////////////////////
    87
                                     PRIVATE METHODS
    88
        //////////////////////////////////////////////////////////////*/
    89
    90
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    91 128×
            return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account);
    92
        }
    93
    }
    59% lib/v2-core/src/hooks/vaults/7540/Deposit7540VaultHook.sol
    Lines covered: 19 / 32 (59%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    8
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    13
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    14
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    15
    import {
    16
        ISuperHookResult,
    17
        ISuperHookInflowOutflow,
    18
        ISuperHookContextAware,
    19
        ISuperHookInspector
    20
    } from "../../../interfaces/ISuperHook.sol";
    21
    22
    /// @title Deposit7540VaultHook
    23
    /// @author Superform Labs
    24
    /// @dev data has the following structure
    25
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    26
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    27
    /// @notice         uint256 amount = BytesLib.toUint256(data, 52);
    28
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    29 296×
    contract Deposit7540VaultHook is BaseHook, VaultBankLockableHook, ISuperHookInflowOutflow, ISuperHookContextAware {
    30
        using HookDataDecoder for bytes;
    31
    32
        uint256 private constant AMOUNT_POSITION = 52;
    33
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    34
    35
        constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC7540) { }
    36
    37
        /*//////////////////////////////////////////////////////////////
    38
                                     VIEW METHODS
    39
        //////////////////////////////////////////////////////////////*/
    40
        /// @inheritdoc BaseHook
    41
        function _buildHookExecutions(
    42
            address prevHook,
    43
            address account,
    44
            bytes calldata data
    45
        )
    46
            internal
    47
            view
    48
            override
    49
            returns (Execution[] memory executions)
    50 0
        {
    51 51×
            address yieldSource = data.extractYieldSource();
    52 51×
            uint256 amount = _decodeAmount(data);
    53 51×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    54
    55
            if (usePrevHookAmount) {
    56 55×
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    57
            }
    58
    59 0
            if (amount == 0) revert AMOUNT_NOT_VALID();
    60 0
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    61
    62 0
            executions = new Execution[](1);
    63 0
            executions[0] = Execution({
    64
                target: yieldSource,
    65
                value: 0,
    66 0
                callData: abi.encodeCall(IERC7540.deposit, (amount, account, account))
    67
            });
    68
        }
    69
    70
        /*//////////////////////////////////////////////////////////////
    71
                                     EXTERNAL METHODS
    72
        //////////////////////////////////////////////////////////////*/
    73
    74
        /// @inheritdoc ISuperHookInflowOutflow
    75 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    76 0
            return _decodeAmount(data);
    77
        }
    78
    79
        /// @inheritdoc ISuperHookContextAware
    80 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    81
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    82
        }
    83
    84
        /// @inheritdoc ISuperHookInspector
    85 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    86 70×
            return abi.encodePacked(data.extractYieldSource());
    87
        }
    88
    89
        /*//////////////////////////////////////////////////////////////
    90
                                     INTERNAL METHODS
    91
        //////////////////////////////////////////////////////////////*/
    92 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    93
            // store current balance
    94 0
            _setOutAmount(_getBalance(account, data), account);
    95 0
            spToken = IERC7540(data.extractYieldSource()).share();
    96
        }
    97
    98 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    99 0
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    100
        }
    101
    102
        /*//////////////////////////////////////////////////////////////
    103
                                     PRIVATE METHODS
    104
        //////////////////////////////////////////////////////////////*/
    105
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    106
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    107
        }
    108
    109
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    110
            return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account);
    111
        }
    112
    }
    100% lib/v2-core/src/hooks/vaults/7540/Redeem7540VaultHook.sol
    Lines covered: 41 / 41 (100%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
    8
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import {
    13
        ISuperHookResultOutflow,
    14
        ISuperHookInflowOutflow,
    15
        ISuperHookOutflow,
    16
        ISuperHookContextAware,
    17
        ISuperHookInspector
    18
    } from "../../../interfaces/ISuperHook.sol";
    19
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    20
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    21
    22
    /// @title Redeem7540VaultHook
    23
    /// @author Superform Labs
    24
    /// @notice This hook does not support tokens reverting on 0 approval
    25
    /// @dev data has the following structure
    26
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    27
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    28
    /// @notice         uint256 shares = BytesLib.toUint256(data, 52);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    30 408×
    contract Redeem7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
    31
        using HookDataDecoder for bytes;
    32
    33
        uint256 private constant AMOUNT_POSITION = 52;
    34
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    35
    36
        constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { }
    37
    38
        /*//////////////////////////////////////////////////////////////
    39
                                     VIEW METHODS
    40
        //////////////////////////////////////////////////////////////*/
    41
        /// @inheritdoc BaseHook
    42 16×
        function _buildHookExecutions(
    43
            address prevHook,
    44
            address account,
    45
            bytes calldata data
    46
        )
    47
            internal
    48
            view
    49
            override
    50
            returns (Execution[] memory executions)
    51
        {
    52 102×
            address yieldSource = data.extractYieldSource();
    53 102×
            uint256 shares = _decodeAmount(data);
    54 102×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    55
    56 10×
            if (usePrevHookAmount) {
    57 55×
                shares = ISuperHookResultOutflow(prevHook).getOutAmount(account);
    58
            }
    59
    60 38×
            if (shares == 0) revert AMOUNT_NOT_VALID();
    61 28×
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    62
    63 68×
            executions = new Execution[](1);
    64 80×
            executions[0] = Execution({
    65
                target: yieldSource,
    66
                value: 0,
    67 52×
                callData: abi.encodeCall(IERC7540.redeem, (shares, account, account))
    68
            });
    69
        }
    70
    71
        /*//////////////////////////////////////////////////////////////
    72
                                     EXTERNAL METHODS
    73
        //////////////////////////////////////////////////////////////*/
    74
        /// @inheritdoc ISuperHookInflowOutflow
    75 24×
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    76
            return _decodeAmount(data);
    77
        }
    78
    79
        /// @inheritdoc ISuperHookContextAware
    80 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    81
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    82
        }
    83
    84
        /// @inheritdoc ISuperHookOutflow
    85 52×
        function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
    86 16×
            return _replaceCalldataAmount(data, amount, AMOUNT_POSITION);
    87
        }
    88
    89
        /// @inheritdoc ISuperHookInspector
    90 34×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    91 140×
            return abi.encodePacked(data.extractYieldSource());
    92
        }
    93
    94
        /*//////////////////////////////////////////////////////////////    
    95
                                     INTERNAL METHODS
    96
        //////////////////////////////////////////////////////////////*/
    97 14×
        function _preExecute(address, address account, bytes calldata data) internal override {
    98 102×
            address yieldSource = data.extractYieldSource();
    99 136×
            asset = IERC7540(yieldSource).asset();
    100 108×
            _setOutAmount(_getBalance(account, data), account);
    101 106×
            usedShares = _getSharesBalance(account, data);
    102 136×
            spToken = IERC7540(yieldSource).share();
    103
        }
    104
    105
        function _postExecute(address, address account, bytes calldata data) internal override {
    106 65×
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    107 60×
            usedShares = usedShares - _getSharesBalance(account, data);
    108
        }
    109
    110
        /*//////////////////////////////////////////////////////////////
    111
                                     PRIVATE METHODS
    112
        //////////////////////////////////////////////////////////////*/
    113
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    114
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    115
        }
    116
    117
        function _getBalance(address account, bytes memory) private view returns (uint256) {
    118 116×
            return IERC20(asset).balanceOf(account);
    119
        }
    120
    121
        function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
    122 16×
            address yieldSource = data.extractYieldSource();
    123 118×
            return IERC7540(yieldSource).claimableRedeemRequest(0, account);
    124
        }
    125
    }
    59% lib/v2-core/src/hooks/vaults/7540/RequestDeposit7540VaultHook.sol
    Lines covered: 19 / 32 (59%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    8
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import {
    13
        ISuperHookResult,
    14
        ISuperHookInflowOutflow,
    15
        ISuperHookAsyncCancelations,
    16
        ISuperHookContextAware,
    17
        ISuperHookInspector
    18
    } from "../../../interfaces/ISuperHook.sol";
    19
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    20
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    21
    22
    /// @title RequestDeposit7540VaultHook
    23
    /// @author Superform Labs
    24
    /// @dev data has the following structure
    25
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    26
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    27
    /// @notice         uint256 amount = BytesLib.toUint256(data, 52);
    28
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    29 296×
    contract RequestDeposit7540VaultHook is
    30
        BaseHook,
    31
        ISuperHookInflowOutflow,
    32
        ISuperHookAsyncCancelations,
    33
        ISuperHookContextAware
    34
    {
    35
        using HookDataDecoder for bytes;
    36
    37
        uint256 private constant AMOUNT_POSITION = 52;
    38
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    39
    40
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { }
    41
    42
        /*//////////////////////////////////////////////////////////////
    43
                                     VIEW METHODS
    44
        //////////////////////////////////////////////////////////////*/
    45
        /// @inheritdoc BaseHook
    46
        function _buildHookExecutions(
    47
            address prevHook,
    48
            address account,
    49
            bytes calldata data
    50
        )
    51
            internal
    52
            view
    53
            override
    54
            returns (Execution[] memory executions)
    55 0
        {
    56 51×
            address yieldSource = data.extractYieldSource();
    57 51×
            uint256 amount = _decodeAmount(data);
    58 51×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    59
    60
            if (usePrevHookAmount) {
    61 55×
                amount = ISuperHookResult(prevHook).getOutAmount(account);
    62
            }
    63
    64 0
            if (amount == 0) revert AMOUNT_NOT_VALID();
    65 0
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    66
    67 0
            executions = new Execution[](1);
    68 0
            executions[0] = Execution({
    69
                target: yieldSource,
    70
                value: 0,
    71 0
                callData: abi.encodeCall(IERC7540.requestDeposit, (amount, account, account))
    72
            });
    73
        }
    74
    75
        /// @inheritdoc ISuperHookAsyncCancelations
    76 0
        function isAsyncCancelHook() external pure returns (CancelationType) {
    77
            return CancelationType.NONE;
    78
        }
    79
    80
        /*//////////////////////////////////////////////////////////////
    81
                                     EXTERNAL METHODS
    82
        //////////////////////////////////////////////////////////////*/
    83
    84
        /// @inheritdoc ISuperHookInflowOutflow
    85 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    86 0
            return _decodeAmount(data);
    87
        }
    88
    89
        /// @inheritdoc ISuperHookContextAware
    90 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    91
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    92
        }
    93
    94
        /// @inheritdoc ISuperHookInspector
    95 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    96 70×
            return abi.encodePacked(data.extractYieldSource());
    97
        }
    98
    99
        /*//////////////////////////////////////////////////////////////
    100
                                     INTERNAL METHODS
    101
        //////////////////////////////////////////////////////////////*/
    102 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    103 0
            _setOutAmount(_getBalance(account, data), account);
    104
        }
    105
    106 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    107 0
            _setOutAmount(getOutAmount(account) - _getBalance(account, data), account);
    108
        }
    109
    110
        /*//////////////////////////////////////////////////////////////
    111
                                     PRIVATE METHODS
    112
        //////////////////////////////////////////////////////////////*/
    113
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    114
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    115
        }
    116
    117
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    118
            return IERC20(IERC7540(data.extractYieldSource()).asset()).balanceOf(account);
    119
        }
    120
    }
    59% lib/v2-core/src/hooks/vaults/7540/RequestRedeem7540VaultHook.sol
    Lines covered: 19 / 32 (59%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    8
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    9
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    10
    11
    // Superform
    12
    import { BaseHook } from "../../BaseHook.sol";
    13
    import {
    14
        ISuperHookResult,
    15
        ISuperHookInflowOutflow,
    16
        ISuperHookAsyncCancelations,
    17
        ISuperHookContextAware,
    18
        ISuperHookInspector
    19
    } from "../../../interfaces/ISuperHook.sol";
    20
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    21
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    22
    23
    /// @title RequestRedeem7540VaultHook
    24
    /// @author Superform Labs
    25
    /// @dev data has the following structure
    26
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    27
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    28
    /// @notice         uint256 shares = BytesLib.toUint256(data, 52);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    30 296×
    contract RequestRedeem7540VaultHook is
    31
        BaseHook,
    32
        ISuperHookInflowOutflow,
    33
        ISuperHookAsyncCancelations,
    34
        ISuperHookContextAware
    35
    {
    36
        using HookDataDecoder for bytes;
    37
    38
        uint256 private constant AMOUNT_POSITION = 52;
    39
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    40
    41
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.ERC7540) { }
    42
    43
        /*//////////////////////////////////////////////////////////////
    44
                                     VIEW METHODS
    45
        //////////////////////////////////////////////////////////////*/
    46
        /// @inheritdoc BaseHook
    47
        function _buildHookExecutions(
    48
            address prevHook,
    49
            address account,
    50
            bytes calldata data
    51
        )
    52
            internal
    53
            view
    54
            override
    55
            returns (Execution[] memory executions)
    56 0
        {
    57 51×
            address yieldSource = data.extractYieldSource();
    58 51×
            uint256 shares = _decodeAmount(data);
    59 51×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    60
    61
            if (usePrevHookAmount) {
    62 55×
                shares = ISuperHookResult(prevHook).getOutAmount(account);
    63
            }
    64
    65 0
            if (shares == 0) revert AMOUNT_NOT_VALID();
    66 0
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    67
    68 0
            executions = new Execution[](1);
    69 0
            executions[0] = Execution({
    70
                target: yieldSource,
    71
                value: 0,
    72 0
                callData: abi.encodeCall(IERC7540.requestRedeem, (shares, account, account))
    73
            });
    74
        }
    75
    76
        /// @inheritdoc ISuperHookAsyncCancelations
    77 0
        function isAsyncCancelHook() external pure returns (CancelationType) {
    78
            return CancelationType.NONE;
    79
        }
    80
    81
        /*//////////////////////////////////////////////////////////////
    82
                                     EXTERNAL METHODS
    83
        //////////////////////////////////////////////////////////////*/
    84
    85
        /// @inheritdoc ISuperHookInflowOutflow
    86 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    87 0
            return _decodeAmount(data);
    88
        }
    89
    90
        /// @inheritdoc ISuperHookContextAware
    91 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    92
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    93
        }
    94
    95
        /// @inheritdoc ISuperHookInspector
    96 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    97 70×
            return abi.encodePacked(data.extractYieldSource());
    98
        }
    99
    100
        /*//////////////////////////////////////////////////////////////
    101
                                     INTERNAL METHODS
    102
        //////////////////////////////////////////////////////////////*/
    103
    104 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    105 0
            _setOutAmount(_getBalance(account, data), account);
    106
        }
    107
    108 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    109 0
            _setOutAmount(getOutAmount(account) - _getBalance(account, data), account);
    110
        }
    111
    112
        /*//////////////////////////////////////////////////////////////
    113
                                     PRIVATE METHODS
    114
        //////////////////////////////////////////////////////////////*/
    115
    116
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    117
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    118
        }
    119
    120
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    121
            return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account);
    122
        }
    123
    }
    51% lib/v2-core/src/hooks/vaults/7540/Withdraw7540VaultHook.sol
    Lines covered: 21 / 41 (51%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
    8
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import {
    13
        ISuperHookResultOutflow,
    14
        ISuperHookInflowOutflow,
    15
        ISuperHookOutflow,
    16
        ISuperHookContextAware,
    17
        ISuperHookInspector
    18
    } from "../../../interfaces/ISuperHook.sol";
    19
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    20
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    21
    22
    /// @title Withdraw7540VaultHook
    23
    /// @author Superform Labs
    24
    /// @notice Compatible only with ERC-7540 vaults where `requestId` is non-fungible
    25
    /// @dev data has the following structure
    26
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    27
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    28
    /// @notice         uint256 amount = BytesLib.toUint256(data, 52);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    30 291×
    contract Withdraw7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
    31
        using HookDataDecoder for bytes;
    32
    33
        uint256 private constant AMOUNT_POSITION = 52;
    34
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    35
    36
        constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { }
    37
    38
        /*//////////////////////////////////////////////////////////////
    39
                                     VIEW METHODS
    40
        //////////////////////////////////////////////////////////////*/
    41
        /// @inheritdoc BaseHook
    42
        function _buildHookExecutions(
    43
            address prevHook,
    44
            address account,
    45
            bytes calldata data
    46
        )
    47
            internal
    48
            view
    49
            override
    50
            returns (Execution[] memory executions)
    51 0
        {
    52 51×
            address yieldSource = data.extractYieldSource();
    53 51×
            uint256 amount = _decodeAmount(data);
    54 51×
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    55
    56
            if (usePrevHookAmount) {
    57 55×
                amount = ISuperHookResultOutflow(prevHook).getOutAmount(account);
    58
            }
    59
    60 0
            if (amount == 0) revert AMOUNT_NOT_VALID();
    61 0
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    62
    63 0
            executions = new Execution[](1);
    64 0
            executions[0] = Execution({
    65
                target: yieldSource,
    66
                value: 0,
    67 0
                callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account))
    68
            });
    69
        }
    70
    71
        /*//////////////////////////////////////////////////////////////
    72
                                     EXTERNAL METHODS
    73
        //////////////////////////////////////////////////////////////*/
    74
    75
        /// @inheritdoc ISuperHookInflowOutflow
    76 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    77 0
            return _decodeAmount(data);
    78
        }
    79
    80
        /// @inheritdoc ISuperHookContextAware
    81 16×
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    82
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    83
        }
    84
    85
        /// @inheritdoc ISuperHookOutflow
    86 14×
        function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
    87
            return _replaceCalldataAmount(data, amount, AMOUNT_POSITION);
    88
        }
    89
    90
        /// @inheritdoc ISuperHookInspector
    91 17×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    92 70×
            return abi.encodePacked(data.extractYieldSource());
    93
        }
    94
    95
        /*//////////////////////////////////////////////////////////////
    96
                                     INTERNAL METHODS
    97
        //////////////////////////////////////////////////////////////*/
    98 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    99 0
            address yieldSource = data.extractYieldSource();
    100 0
            asset = IERC7540(yieldSource).asset();
    101 0
            _setOutAmount(_getBalance(account, data), account);
    102 0
            usedShares = _getSharesBalance(account, data);
    103 0
            spToken = IERC7540(yieldSource).share();
    104
        }
    105
    106 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    107 0
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    108 0
            usedShares = usedShares - _getSharesBalance(account, data);
    109
        }
    110
    111
        /*//////////////////////////////////////////////////////////////
    112
                                     PRIVATE METHODS
    113
        //////////////////////////////////////////////////////////////*/
    114
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    115
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    116
        }
    117
    118 0
        function _getBalance(address account, bytes memory) private view returns (uint256) {
    119 0
            return IERC20(asset).balanceOf(account);
    120
        }
    121
    122
        function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
    123 0
            address yieldSource = data.extractYieldSource();
    124
            return IERC7540(yieldSource).claimableRedeemRequest(0, account);
    125
        }
    126
    }
    85% lib/v2-core/src/hooks/vaults/super-vault/CancelRedeemHook.sol
    Lines covered: 17 / 20 (85%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    6
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    8
    9
    // Superform
    10
    import { BaseHook } from "../../BaseHook.sol";
    11
    import { VaultBankLockableHook } from "../../VaultBankLockableHook.sol";
    12
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    13
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    14
    import { ISuperVault } from "../../../vendor/superform/ISuperVault.sol";
    15
    import { ISuperHookAsyncCancelations, ISuperHookInspector } from "../../../interfaces/ISuperHook.sol";
    16
    17
    /// @title CancelRedeemHook
    18
    /// @author Superform Labs
    19
    /// @dev data has the following structure
    20
    /// @notice         bytes32 placeholder = bytes32(BytesLib.slice(data, 0, 32), 0);
    21
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    22 256×
    contract CancelRedeemHook is BaseHook, VaultBankLockableHook, ISuperHookAsyncCancelations {
    23
        using HookDataDecoder for bytes;
    24
    25
        constructor() BaseHook(HookType.NONACCOUNTING, HookSubTypes.CANCEL_REDEEM) { }
    26
    27
        /*//////////////////////////////////////////////////////////////
    28
                                     VIEW METHODS
    29
        //////////////////////////////////////////////////////////////*/
    30
        function _buildHookExecutions(
    31
            address,
    32
            address account,
    33
            bytes calldata data
    34
        )
    35
            internal
    36
            pure
    37
            override
    38
            returns (Execution[] memory executions)
    39
        {
    40 50×
            address yieldSource = data.extractYieldSource();
    41
    42 14×
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    43
    44 34×
            executions = new Execution[](1);
    45
            executions[0] =
    46 50×
                Execution({ target: yieldSource, value: 0, callData: abi.encodeCall(ISuperVault.cancelRedeem, (account)) });
    47
        }
    48
    49
        /*//////////////////////////////////////////////////////////////
    50
                                     EX`TERNAL METHODS
    51
        //////////////////////////////////////////////////////////////*/
    52
    53
        /// @inheritdoc ISuperHookAsyncCancelations
    54 0
        function isAsyncCancelHook() external pure returns (CancelationType) {
    55 0
            return CancelationType.OUTFLOW;
    56
        }
    57
    58
        /// @inheritdoc ISuperHookInspector
    59 25×
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    60 70×
            return abi.encodePacked(data.extractYieldSource());
    61
        }
    62
    63
        /*//////////////////////////////////////////////////////////////
    64
                                     INTERNAL METHODS
    65
        //////////////////////////////////////////////////////////////*/
    66
        function _preExecute(address, address account, bytes calldata data) internal override {
    67 50×
            _setOutAmount(_getBalance(account, data), account);
    68 58×
            spToken = data.extractYieldSource();
    69
        }
    70
    71 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    72
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    73
        }
    74
        /*//////////////////////////////////////////////////////////////
    75
                                     PRIVATE METHODS
    76
        //////////////////////////////////////////////////////////////*/
    77
    78
        function _getBalance(address account, bytes memory data) private view returns (uint256) {
    79 128×
            return IERC20(IERC7540(data.extractYieldSource()).share()).balanceOf(account);
    80
        }
    81
    }
    4% lib/v2-core/src/hooks/vaults/super-vault/Withdraw7540VaultHook.sol
    Lines covered: 2 / 41 (4%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../../../vendor/BytesLib.sol";
    6
    import { Execution } from "modulekit/accounts/erc7579/lib/ExecutionLib.sol";
    7
    import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
    8
    import { IERC7540 } from "../../../vendor/vaults/7540/IERC7540.sol";
    9
    10
    // Superform
    11
    import { BaseHook } from "../../BaseHook.sol";
    12
    import {
    13
        ISuperHookResultOutflow,
    14
        ISuperHookInflowOutflow,
    15
        ISuperHookOutflow,
    16
        ISuperHookContextAware,
    17
        ISuperHookInspector
    18
    } from "../../../interfaces/ISuperHook.sol";
    19
    import { HookSubTypes } from "../../../libraries/HookSubTypes.sol";
    20
    import { HookDataDecoder } from "../../../libraries/HookDataDecoder.sol";
    21
    22
    /// @title Withdraw7540VaultHook
    23
    /// @author Superform Labs
    24
    /// @notice Compatible only with ERC-7540 vaults where `requestId` is non-fungible
    25
    /// @dev data has the following structure
    26
    /// @notice         bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
    27
    /// @notice         address yieldSource = BytesLib.toAddress(data, 32);
    28
    /// @notice         uint256 amount = BytesLib.toUint256(data, 52);
    29
    /// @notice         bool usePrevHookAmount = _decodeBool(data, 84);
    30 224×
    contract Withdraw7540VaultHook is BaseHook, ISuperHookInflowOutflow, ISuperHookOutflow, ISuperHookContextAware {
    31
        using HookDataDecoder for bytes;
    32
    33 0
        uint256 private constant AMOUNT_POSITION = 52;
    34 0
        uint256 private constant USE_PREV_HOOK_AMOUNT_POSITION = 84;
    35
    36
        constructor() BaseHook(HookType.OUTFLOW, HookSubTypes.ERC7540) { }
    37
    38
        /*//////////////////////////////////////////////////////////////
    39
                                     VIEW METHODS
    40
        //////////////////////////////////////////////////////////////*/
    41
        /// @inheritdoc BaseHook
    42 0
        function _buildHookExecutions(
    43
            address prevHook,
    44
            address account,
    45
            bytes calldata data
    46
        )
    47
            internal
    48
            view
    49
            override
    50 0
            returns (Execution[] memory executions)
    51 0
        {
    52 0
            address yieldSource = data.extractYieldSource();
    53 0
            uint256 amount = _decodeAmount(data);
    54 0
            bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    55
    56 0
            if (usePrevHookAmount) {
    57 0
                amount = ISuperHookResultOutflow(prevHook).getOutAmount(account);
    58
            }
    59
    60 0
            if (amount == 0) revert AMOUNT_NOT_VALID();
    61 0
            if (yieldSource == address(0) || account == address(0)) revert ADDRESS_NOT_VALID();
    62
    63 0
            executions = new Execution[](1);
    64 0
            executions[0] = Execution({
    65
                target: yieldSource,
    66
                value: 0,
    67 0
                callData: abi.encodeCall(IERC7540.withdraw, (amount, account, account))
    68
            });
    69
        }
    70
    71
        /*//////////////////////////////////////////////////////////////
    72
                                     EXTERNAL METHODS
    73
        //////////////////////////////////////////////////////////////*/
    74
    75
        /// @inheritdoc ISuperHookInflowOutflow
    76 0
        function decodeAmount(bytes memory data) external pure returns (uint256) {
    77 0
            return _decodeAmount(data);
    78
        }
    79
    80
        /// @inheritdoc ISuperHookContextAware
    81 0
        function decodeUsePrevHookAmount(bytes memory data) external pure returns (bool) {
    82 0
            return _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
    83
        }
    84
    85
        /// @inheritdoc ISuperHookOutflow
    86 0
        function replaceCalldataAmount(bytes memory data, uint256 amount) external pure returns (bytes memory) {
    87 0
            return _replaceCalldataAmount(data, amount, AMOUNT_POSITION);
    88
        }
    89
    90
        /// @inheritdoc ISuperHookInspector
    91 0
        function inspect(bytes calldata data) external pure override returns (bytes memory) {
    92 0
            return abi.encodePacked(data.extractYieldSource());
    93
        }
    94
    95
        /*//////////////////////////////////////////////////////////////
    96
                                     INTERNAL METHODS
    97
        //////////////////////////////////////////////////////////////*/
    98 0
        function _preExecute(address, address account, bytes calldata data) internal override {
    99 0
            address yieldSource = data.extractYieldSource();
    100 0
            asset = IERC7540(yieldSource).asset();
    101 0
            _setOutAmount(_getBalance(account, data), account);
    102 0
            usedShares = _getSharesBalance(account, data);
    103 0
            spToken = IERC7540(yieldSource).share();
    104
        }
    105
    106 0
        function _postExecute(address, address account, bytes calldata data) internal override {
    107 0
            _setOutAmount(_getBalance(account, data) - getOutAmount(account), account);
    108 0
            usedShares = usedShares - _getSharesBalance(account, data);
    109
        }
    110
    111
        /*//////////////////////////////////////////////////////////////
    112
                                     PRIVATE METHODS
    113
        //////////////////////////////////////////////////////////////*/
    114 0
        function _decodeAmount(bytes memory data) private pure returns (uint256) {
    115 0
            return BytesLib.toUint256(data, AMOUNT_POSITION);
    116
        }
    117
    118 0
        function _getBalance(address account, bytes memory) private view returns (uint256) {
    119 0
            return IERC20(asset).balanceOf(account);
    120
        }
    121
    122 0
        function _getSharesBalance(address account, bytes memory data) private view returns (uint256) {
    123 0
            address yieldSource = data.extractYieldSource();
    124 0
            return IERC7540(yieldSource).claimableRedeemRequest(0, account);
    125
        }
    126
    }
    40% lib/v2-core/src/libraries/HookDataDecoder.sol
    Lines covered: 2 / 5 (40%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    import { BytesLib } from "../vendor/BytesLib.sol";
    5
    6
    /// @title HookDataDecoder
    7
    /// @author Superform Labs
    8
    /// @notice Library for decoding hook data
    9 0
    library HookDataDecoder {
    10 0
        function extractYieldSourceOracleId(bytes memory data) internal pure returns (bytes32) {
    11 0
            return bytes32(BytesLib.slice(data, 0, 32));
    12
        }
    13
    14 50×
        function extractYieldSource(bytes memory data) internal pure returns (address) {
    15 99×
            return BytesLib.toAddress(data, 32);
    16
        }
    17
    }
    34% lib/v2-core/src/libraries/HookSubTypes.sol
    Lines covered: 8 / 23 (34%)
    1
    // SPDX-License-Identifier: Apache-2.0
    2
    pragma solidity 0.8.30;
    3
    4
    /// @title HookSubTypes
    5
    /// @author Superform Labs
    6
    /// @notice Library for hook subtypes
    7 0
    library HookSubTypes {
    8 0
        bytes32 public constant BRIDGE = keccak256(bytes("Bridge"));
    9 0
        bytes32 public constant CANCEL_DEPOSIT = keccak256(bytes("CancelDeposit"));
    10 19×
        bytes32 public constant CANCEL_DEPOSIT_REQUEST = keccak256(bytes("CancelDepositRequest"));
    11 18×
        bytes32 public constant CANCEL_REDEEM = keccak256(bytes("CancelRedeem"));
    12 19×
        bytes32 public constant CANCEL_REDEEM_REQUEST = keccak256(bytes("CancelRedeemRequest"));
    13 0
        bytes32 public constant CLAIM = keccak256(bytes("Claim"));
    14 19×
        bytes32 public constant CLAIM_CANCEL_DEPOSIT_REQUEST = keccak256(bytes("ClaimCancelDepositRequest"));
    15 19×
        bytes32 public constant CLAIM_CANCEL_REDEEM_REQUEST = keccak256(bytes("ClaimCancelRedeemRequest"));
    16 0
        bytes32 public constant COOLDOWN = keccak256(bytes("Cooldown"));
    17 0
        bytes32 public constant ETHENA = keccak256(bytes("Ethena"));
    18 54×
        bytes32 public constant ERC4626 = keccak256(bytes("ERC4626"));
    19 54×
        bytes32 public constant ERC5115 = keccak256(bytes("ERC5115"));
    20 126×
        bytes32 public constant ERC7540 = keccak256(bytes("ERC7540"));
    21 0
        bytes32 public constant LOAN = keccak256(bytes("Loan"));
    22 0
        bytes32 public constant LOAN_REPAY = keccak256(bytes("LoanRepay"));
    23 0
        bytes32 public constant MISC = keccak256(bytes("Misc"));
    24 0
        bytes32 public constant STAKE = keccak256(bytes("Stake"));
    25 0
        bytes32 public constant SWAP = keccak256(bytes("Swap"));
    26 0
        bytes32 public constant TOKEN = keccak256(bytes("Token"));
    27 0
        bytes32 public constant UNSTAKE = keccak256(bytes("Unstake"));
    28 0
        bytes32 public constant PTYT = keccak256(bytes("PTYT"));
    29 0
        bytes32 public constant VAULT_BANK = keccak256(bytes("VaultBank"));
    30
    31
        function getHookSubType(string memory hookSubtype) internal pure returns (bytes32) {
    32
            return keccak256(bytes(hookSubtype));
    33
        }
    34
    }
    15% lib/v2-core/src/vendor/BytesLib.sol
    Lines covered: 6 / 40 (15%)
    1
    // SPDX-License-Identifier: Unlicense
    2
    /*
    3
     * @title Solidity Bytes Arrays Utils
    4
     * @author Gonçalo Sá <goncalo.sa@consensys.net>
    5
     *
    6
     * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
    7
     *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
    8
     */
    9
    pragma solidity 0.8.30;
    10
    11 0
    library BytesLib {
    12
        function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {
    13
            bytes memory tempBytes;
    14
    15
            assembly {
    16
                // Get a location of some free memory and store it in tempBytes as
    17
                // Solidity does for memory variables.
    18
                tempBytes := mload(0x40)
    19
    20
                // Store the length of the first bytes array at the beginning of
    21
                // the memory for tempBytes.
    22
                let length := mload(_preBytes)
    23
                mstore(tempBytes, length)
    24
    25
                // Maintain a memory counter for the current write location in the
    26
                // temp bytes array by adding the 32 bytes for the array length to
    27
                // the starting location.
    28
                let mc := add(tempBytes, 0x20)
    29
                // Stop copying when the memory counter reaches the length of the
    30
                // first bytes array.
    31
                let end := add(mc, length)
    32
    33
                for {
    34
                    // Initialize a copy counter to the start of the _preBytes data,
    35
                    // 32 bytes into its memory.
    36
                    let cc := add(_preBytes, 0x20)
    37
                } lt(mc, end) {
    38
                    // Increase both counters by 32 bytes each iteration.
    39
                    mc := add(mc, 0x20)
    40
                    cc := add(cc, 0x20)
    41
                } {
    42
                    // Write the _preBytes data into the tempBytes memory 32 bytes
    43
                    // at a time.
    44
                    mstore(mc, mload(cc))
    45
                }
    46
    47
                // Add the length of _postBytes to the current length of tempBytes
    48
                // and store it as the new length in the first 32 bytes of the
    49
                // tempBytes memory.
    50
                length := mload(_postBytes)
    51
                mstore(tempBytes, add(length, mload(tempBytes)))
    52
    53
                // Move the memory counter back from a multiple of 0x20 to the
    54
                // actual end of the _preBytes data.
    55
                mc := end
    56
                // Stop copying when the memory counter reaches the new combined
    57
                // length of the arrays.
    58
                end := add(mc, length)
    59
    60
                for { let cc := add(_postBytes, 0x20) } lt(mc, end) {
    61
                    mc := add(mc, 0x20)
    62
                    cc := add(cc, 0x20)
    63
                } { mstore(mc, mload(cc)) }
    64
    65
                // Update the free-memory pointer by padding our last write location
    66
                // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
    67
                // next 32 byte block, then round down to the nearest multiple of
    68
                // 32. If the sum of the length of the two arrays is zero then add
    69
                // one before rounding down to leave a blank 32 bytes (the length block with 0).
    70
                mstore(
    71
                    0x40,
    72
                    and(
    73
                        add(add(end, iszero(add(length, mload(_preBytes)))), 31),
    74
                        not(31) // Round down to the nearest 32 bytes.
    75
                    )
    76
                )
    77
            }
    78
    79
            return tempBytes;
    80
        }
    81
    82
        function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
    83
            assembly {
    84
                // Read the first 32 bytes of _preBytes storage, which is the length
    85
                // of the array. (We don't need to use the offset into the slot
    86
                // because arrays use the entire slot.)
    87
                let fslot := sload(_preBytes.slot)
    88
                // Arrays of 31 bytes or less have an even value in their slot,
    89
                // while longer arrays have an odd value. The actual length is
    90
                // the slot divided by two for odd values, and the lowest order
    91
                // byte divided by two for even values.
    92
                // If the slot is even, bitwise and the slot with 255 and divide by
    93
                // two to get the length. If the slot is odd, bitwise and the slot
    94
                // with -1 and divide by two.
    95
                let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
    96
                let mlength := mload(_postBytes)
    97
                let newlength := add(slength, mlength)
    98
                // slength can contain both the length and contents of the array
    99
                // if length < 32 bytes so let's prepare for that
    100
                // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
    101
                switch add(lt(slength, 32), lt(newlength, 32))
    102
                case 2 {
    103
                    // Since the new array still fits in the slot, we just need to
    104
                    // update the contents of the slot.
    105
                    // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
    106
                    sstore(
    107
                        _preBytes.slot,
    108
                        // all the modifications to the slot are inside this
    109
                        // next block
    110
                        add(
    111
                            // we can just add to the slot contents because the
    112
                            // bytes we want to change are the LSBs
    113
                            fslot,
    114
                            add(
    115
                                mul(
    116
                                    div(
    117
                                        // load the bytes from memory
    118
                                        mload(add(_postBytes, 0x20)),
    119
                                        // zero all bytes to the right
    120
                                        exp(0x100, sub(32, mlength))
    121
                                    ),
    122
                                    // and now shift left the number of bytes to
    123
                                    // leave space for the length in the slot
    124
                                    exp(0x100, sub(32, newlength))
    125
                                ),
    126
                                // increase length by the double of the memory
    127
                                // bytes length
    128
                                mul(mlength, 2)
    129
                            )
    130
                        )
    131
                    )
    132
                }
    133
                case 1 {
    134
                    // The stored value fits in the slot, but the combined value
    135
                    // will exceed it.
    136
                    // get the keccak hash to get the contents of the array
    137
                    mstore(0x0, _preBytes.slot)
    138
                    let sc := add(keccak256(0x0, 0x20), div(slength, 32))
    139
    140
                    // save new length
    141
                    sstore(_preBytes.slot, add(mul(newlength, 2), 1))
    142
    143
                    // The contents of the _postBytes array start 32 bytes into
    144
                    // the structure. Our first read should obtain the `submod`
    145
                    // bytes that can fit into the unused space in the last word
    146
                    // of the stored array. To get this, we read 32 bytes starting
    147
                    // from `submod`, so the data we read overlaps with the array
    148
                    // contents by `submod` bytes. Masking the lowest-order
    149
                    // `submod` bytes allows us to add that value directly to the
    150
                    // stored value.
    151
    152
                    let submod := sub(32, slength)
    153
                    let mc := add(_postBytes, submod)
    154
                    let end := add(_postBytes, mlength)
    155
                    let mask := sub(exp(0x100, submod), 1)
    156
    157
                    sstore(
    158
                        sc,
    159
                        add(
    160
                            and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00),
    161
                            and(mload(mc), mask)
    162
                        )
    163
                    )
    164
    165
                    for {
    166
                        mc := add(mc, 0x20)
    167
                        sc := add(sc, 1)
    168
                    } lt(mc, end) {
    169
                        sc := add(sc, 1)
    170
                        mc := add(mc, 0x20)
    171
                    } { sstore(sc, mload(mc)) }
    172
    173
                    mask := exp(0x100, sub(mc, end))
    174
    175
                    sstore(sc, mul(div(mload(mc), mask), mask))
    176
                }
    177
                default {
    178
                    // get the keccak hash to get the contents of the array
    179
                    mstore(0x0, _preBytes.slot)
    180
                    // Start copying to the last used word of the stored array.
    181
                    let sc := add(keccak256(0x0, 0x20), div(slength, 32))
    182
    183
                    // save new length
    184
                    sstore(_preBytes.slot, add(mul(newlength, 2), 1))
    185
    186
                    // Copy over the first `submod` bytes of the new data as in
    187
                    // case 1 above.
    188
                    let slengthmod := mod(slength, 32)
    189
                    let mlengthmod := mod(mlength, 32)
    190
                    let submod := sub(32, slengthmod)
    191
                    let mc := add(_postBytes, submod)
    192
                    let end := add(_postBytes, mlength)
    193
                    let mask := sub(exp(0x100, submod), 1)
    194
    195
                    sstore(sc, add(sload(sc), and(mload(mc), mask)))
    196
    197
                    for {
    198
                        sc := add(sc, 1)
    199
                        mc := add(mc, 0x20)
    200
                    } lt(mc, end) {
    201
                        sc := add(sc, 1)
    202
                        mc := add(mc, 0x20)
    203
                    } { sstore(sc, mload(mc)) }
    204
    205
                    mask := exp(0x100, sub(mc, end))
    206
    207
                    sstore(sc, mul(div(mload(mc), mask), mask))
    208
                }
    209
            }
    210
        }
    211
    212 0
        function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
    213
            // We're using the unchecked block below because otherwise execution ends
    214
            // with the native overflow error code.
    215
            unchecked {
    216 0
                require(_length + 31 >= _length, "slice_overflow");
    217
            }
    218 0
            require(_bytes.length >= _start + _length, "slice_outOfBounds");
    219
    220 0
            bytes memory tempBytes;
    221
    222
            assembly {
    223 0
                switch iszero(_length)
    224 0
                case 0 {
    225
                    // Get a location of some free memory and store it in tempBytes as
    226
                    // Solidity does for memory variables.
    227 0
                    tempBytes := mload(0x40)
    228
    229
                    // The first word of the slice result is potentially a partial
    230
                    // word read from the original array. To read it, we calculate
    231
                    // the length of that partial word and start copying that many
    232
                    // bytes into the array. The first word we copy will start with
    233
                    // data we don't care about, but the last `lengthmod` bytes will
    234
                    // land at the beginning of the contents of the new array. When
    235
                    // we're done copying, we overwrite the full first word with
    236
                    // the actual length of the slice.
    237 0
                    let lengthmod := and(_length, 31)
    238
    239
                    // The multiplication in the next line is necessary
    240
                    // because when slicing multiples of 32 bytes (lengthmod == 0)
    241
                    // the following copy loop was copying the origin's length
    242
                    // and then ending prematurely not copying everything it should.
    243 0
                    let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
    244 0
                    let end := add(mc, _length)
    245
    246 0
                    for {
    247
                        // The multiplication in the next line has the same exact purpose
    248
                        // as the one above.
    249 0
                        let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
    250 0
                    } lt(mc, end) {
    251 0
                        mc := add(mc, 0x20)
    252 0
                        cc := add(cc, 0x20)
    253 0
                    } { mstore(mc, mload(cc)) }
    254
    255 0
                    mstore(tempBytes, _length)
    256
    257
                    //update free-memory pointer
    258
                    //allocating the array padded to 32 bytes like the compiler does now
    259 0
                    mstore(0x40, and(add(mc, 31), not(31)))
    260
                }
    261
                //if we want a zero-length slice let's just return a zero-length array
    262
                default {
    263 0
                    tempBytes := mload(0x40)
    264
                    //zero out the 32 bytes slice we are about to return
    265
                    //we need to do it because Solidity does not garbage collect
    266 0
                    mstore(tempBytes, 0)
    267
    268 0
                    mstore(0x40, add(tempBytes, 0x20))
    269
                }
    270
            }
    271
    272 0
            return tempBytes;
    273
        }
    274
    275 75×
        function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
    276 357×
            require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
    277
            address tempAddress;
    278
    279
            assembly {
    280 175×
                tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
    281
            }
    282
    283
            return tempAddress;
    284
        }
    285
    286 0
        function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
    287 0
            require(_bytes.length >= _start + 1, "toUint8_outOfBounds");
    288
            uint8 tempUint;
    289
    290
            assembly {
    291 0
                tempUint := mload(add(add(_bytes, 0x1), _start))
    292
            }
    293
    294
            return tempUint;
    295
        }
    296
    297
        function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
    298
            require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
    299
            uint16 tempUint;
    300
    301
            assembly {
    302
                tempUint := mload(add(add(_bytes, 0x2), _start))
    303
            }
    304
    305
            return tempUint;
    306
        }
    307
    308 0
        function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
    309 0
            require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
    310
            uint32 tempUint;
    311
    312
            assembly {
    313 0
                tempUint := mload(add(add(_bytes, 0x4), _start))
    314
            }
    315
    316
            return tempUint;
    317
        }
    318
    319 0
        function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
    320 0
            require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
    321
            uint64 tempUint;
    322
    323
            assembly {
    324 0
                tempUint := mload(add(add(_bytes, 0x8), _start))
    325
            }
    326
    327
            return tempUint;
    328
        }
    329
    330
        function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
    331
            require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
    332
            uint96 tempUint;
    333
    334
            assembly {
    335
                tempUint := mload(add(add(_bytes, 0xc), _start))
    336
            }
    337
    338
            return tempUint;
    339
        }
    340
    341
        function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
    342
            require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
    343
            uint128 tempUint;
    344
    345
            assembly {
    346
                tempUint := mload(add(add(_bytes, 0x10), _start))
    347
            }
    348
    349
            return tempUint;
    350
        }
    351
    352 54×
        function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
    353 266×
            require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
    354
            uint256 tempUint;
    355
    356
            assembly {
    357 90×
                tempUint := mload(add(add(_bytes, 0x20), _start))
    358
            }
    359
    360
            return tempUint;
    361
        }
    362
    363 0
        function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
    364 0
            require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
    365
            bytes32 tempBytes32;
    366
    367
            assembly {
    368
                tempBytes32 := mload(add(add(_bytes, 0x20), _start))
    369
            }
    370
    371
            return tempBytes32;
    372
        }
    373
    374
        function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
    375
            bool success = true;
    376
    377
            assembly {
    378
                let length := mload(_preBytes)
    379
    380
                // if lengths don't match the arrays are not equal
    381
                switch eq(length, mload(_postBytes))
    382
                case 1 {
    383
                    // cb is a circuit breaker in the for loop since there's
    384
                    //  no said feature for inline assembly loops
    385
                    // cb = 1 - don't breaker
    386
                    // cb = 0 - break
    387
                    let cb := 1
    388
    389
                    let mc := add(_preBytes, 0x20)
    390
                    let end := add(mc, length)
    391
    392
                    for { let cc := add(_postBytes, 0x20) }
    393
                    // the next line is the loop condition:
    394
                    // while(uint256(mc < end) + cb == 2)
    395
                    eq(add(lt(mc, end), cb), 2) {
    396
                        mc := add(mc, 0x20)
    397
                        cc := add(cc, 0x20)
    398
                    } {
    399
                        // if any of these checks fails then arrays are not equal
    400
                        if iszero(eq(mload(mc), mload(cc))) {
    401
                            // unsuccess:
    402
                            success := 0
    403
                            cb := 0
    404
                        }
    405
                    }
    406
                }
    407
                default {
    408
                    // unsuccess:
    409
                    success := 0
    410
                }
    411
            }
    412
    413
            return success;
    414
        }
    415
    416
        function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
    417
            bool success = true;
    418
    419
            assembly {
    420
                // we know _preBytes_offset is 0
    421
                let fslot := sload(_preBytes.slot)
    422
                // Decode the length of the stored array like in concatStorage().
    423
                let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
    424
                let mlength := mload(_postBytes)
    425
    426
                // if lengths don't match the arrays are not equal
    427
                switch eq(slength, mlength)
    428
                case 1 {
    429
                    // slength can contain both the length and contents of the array
    430
                    // if length < 32 bytes so let's prepare for that
    431
                    // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
    432
                    if iszero(iszero(slength)) {
    433
                        switch lt(slength, 32)
    434
                        case 1 {
    435
                            // blank the last byte which is the length
    436
                            fslot := mul(div(fslot, 0x100), 0x100)
    437
    438
                            if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
    439
                                // unsuccess:
    440
                                success := 0
    441
                            }
    442
                        }
    443
                        default {
    444
                            // cb is a circuit breaker in the for loop since there's
    445
                            //  no said feature for inline assembly loops
    446
                            // cb = 1 - don't breaker
    447
                            // cb = 0 - break
    448
                            let cb := 1
    449
    450
                            // get the keccak hash to get the contents of the array
    451
                            mstore(0x0, _preBytes.slot)
    452
                            let sc := keccak256(0x0, 0x20)
    453
    454
                            let mc := add(_postBytes, 0x20)
    455
                            let end := add(mc, mlength)
    456
    457
                            // the next line is the loop condition:
    458
                            // while(uint256(mc < end) + cb == 2)
    459
                            for { } eq(add(lt(mc, end), cb), 2) {
    460
                                sc := add(sc, 1)
    461
                                mc := add(mc, 0x20)
    462
                            } {
    463
                                if iszero(eq(sload(sc), mload(mc))) {
    464
                                    // unsuccess:
    465
                                    success := 0
    466
                                    cb := 0
    467
                                }
    468
                            }
    469
                        }
    470
                    }
    471
                }
    472
                default {
    473
                    // unsuccess:
    474
                    success := 0
    475
                }
    476
            }
    477
    478
            return success;
    479
        }
    480
    }
    0% src/Bank.sol
    Lines covered: 0 / 35 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
    6
    import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
    7
    8
    // Superform
    9
    import { IHookExecutionData } from "./interfaces/IHookExecutionData.sol";
    10
    import { ISuperHook, Execution } from "@superform-v2-core/src/interfaces/ISuperHook.sol";
    11
    12
    abstract contract Bank is ReentrancyGuard {
    13
        /*//////////////////////////////////////////////////////////////
    14
                                    ERRORS
    15
        //////////////////////////////////////////////////////////////*/
    16
        error INVALID_HOOK();
    17
        error INVALID_MERKLE_PROOF();
    18
        error HOOK_EXECUTION_FAILED();
    19
        error ZERO_LENGTH_ARRAY();
    20
        error INVALID_ARRAY_LENGTH();
    21
    22
        /*//////////////////////////////////////////////////////////////
    23
                                    EVENTS
    24
        //////////////////////////////////////////////////////////////*/
    25
        /// @notice Emitted when hooks are executed.
    26
        /// @param hooks The addresses of the hooks that were executed.
    27
        /// @param data The data passed to each hook.
    28
        event HooksExecuted(address[] hooks, bytes[] data);
    29
    30
        /*//////////////////////////////////////////////////////////////
    31
                                    INTERNAL FUNCTIONS
    32
        //////////////////////////////////////////////////////////////*/
    33
        function _getMerkleRootForHook(address hookAddress) internal view virtual returns (bytes32);
    34
    35 0
        function _executeHooks(IHookExecutionData.HookExecutionData calldata executionData) internal virtual nonReentrant {
    36 0
            uint256 hooksLength = executionData.hooks.length;
    37 0
            if (hooksLength == 0) revert ZERO_LENGTH_ARRAY();
    38
    39
            // Validate arrays have matching lengths
    40 0
            if (hooksLength != executionData.data.length || hooksLength != executionData.merkleProofs.length) {
    41 0
                revert INVALID_ARRAY_LENGTH();
    42
            }
    43
    44 0
            address prevHook;
    45 0
            address hookAddress;
    46 0
            bytes memory hookData;
    47 0
            bytes32[] memory merkleProof;
    48 0
            ISuperHook hook;
    49 0
            bytes32 merkleRoot;
    50 0
            Execution[] memory executions;
    51 0
            Execution memory executionStep;
    52 0
            bytes32 targetLeaf;
    53 0
            bool success;
    54
    55 0
            for (uint256 i; i < hooksLength; i++) {
    56 0
                hookAddress = executionData.hooks[i];
    57 0
                hookData = executionData.data[i];
    58 0
                merkleProof = executionData.merkleProofs[i];
    59
    60 0
                hook = ISuperHook(hookAddress);
    61
    62
                // 1. Get the Merkle root specific to this hook
    63 0
                merkleRoot = _getMerkleRootForHook(hookAddress);
    64
    65 0
                ISuperHook(hookAddress).setExecutionContext(address(this));
    66
    67
                // 2. Build Execution Steps
    68 0
                executions = hook.build(prevHook, address(this), hookData);
    69
    70
                // 3. Execute Target Calls and verify each target
    71 0
                for (uint256 j; j < executions.length; ++j) {
    72 0
                    executionStep = executions[j];
    73
    74
                    // valid hooks encapsulate execution between a `.preExecute` and ` .postExecute`
    75
                    // target for preExecute and postExecute is the hook address
    76
                    // keep the original behavior for validating the tree against the actual execution steps
    77 0
                    if (executionStep.target != hookAddress) {
    78
                        // Verify that this target is allowed for this hook using the Merkle proof
    79
                        // The leaf is the hash of the target address
    80 0
                        targetLeaf = keccak256(bytes.concat(keccak256(abi.encodePacked(executionStep.target))));
    81
                        // Verify this target is allowed using the corresponding Merkle proof
    82 0
                        if (!MerkleProof.verify(merkleProof, merkleRoot, targetLeaf)) {
    83 0
                            revert INVALID_MERKLE_PROOF();
    84
                        }
    85
                    }
    86
    87
                    // Execute the call after verification
    88 0
                    (success,) = executionStep.target.call{ value: executionStep.value }(executionStep.callData);
    89 0
                    if (!success) {
    90 0
                        revert HOOK_EXECUTION_FAILED();
    91
                    }
    92
                }
    93
    94
                // Reset execution state after each hook
    95 0
                ISuperHook(hookAddress).resetExecutionState(address(this));
    96
    97 0
                prevHook = hookAddress;
    98
            }
    99
    100 0
            emit HooksExecuted(executionData.hooks, executionData.data);
    101
        }
    102
    }
    0% src/SuperAsset/IncentiveCalculationContract.sol
    Lines covered: 0 / 39 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    5
    import { IIncentiveCalculationContract } from "../interfaces/SuperAsset/IIncentiveCalculationContract.sol";
    6
    7
    /// @title IncentiveCalculationContract
    8
    /// @author Superform Labs
    9
    /// @notice A stateless contract for calculating incentives.
    10 0
    contract IncentiveCalculationContract is IIncentiveCalculationContract {
    11
        using Math for uint256;
    12
    13
        // --- Constants ---
    14 0
        uint256 public constant PRECISION = 1e18;
    15 0
        uint256 public constant PERC = 100e18;
    16
    17
        // --- View Functions ---
    18
        /// @inheritdoc IIncentiveCalculationContract
    19 0
        function energy(
    20
            uint256[] memory currentAllocation,
    21
            uint256[] memory allocationTarget,
    22
            uint256[] memory weights,
    23
            uint256 totalCurrentAllocation,
    24
            uint256 totalAllocationTarget
    25
        )
    26
            public
    27
            pure
    28 0
            returns (uint256 res, bool isSuccess)
    29
        {
    30 0
            if (currentAllocation.length != allocationTarget.length || currentAllocation.length != weights.length) {
    31
                // NOTE: This is a really corner a case that should never happen, this is why we let it revert even though
    32
                // in general we do not allow view and pure functions to revert.
    33 0
                revert INVALID_ARRAY_LENGTH();
    34
            }
    35
    36
            // NOTE: This is to ensure we won't divide by zero in the subsequent calculations
    37 0
            if (totalCurrentAllocation == 0 || totalAllocationTarget == 0) {
    38 0
                return (0, false);
    39
            }
    40
    41 0
            uint256 length = currentAllocation.length;
    42 0
            for (uint256 i; i < length; i++) {
    43 0
                uint256 _currentAllocation = Math.mulDiv(currentAllocation[i], PERC, totalCurrentAllocation);
    44 0
                uint256 _targetAllocation = Math.mulDiv(allocationTarget[i], PERC, totalAllocationTarget);
    45 0
                int256 diff = int256(_currentAllocation) - int256(_targetAllocation);
    46
                // Square the difference and maintain precision
    47 0
                uint256 diff2 = Math.mulDiv(uint256(diff * diff), 1, PRECISION);
    48
                // Apply weight and maintain precision
    49 0
                res += Math.mulDiv(diff2, weights[i], PRECISION);
    50
            }
    51 0
            return (res, true);
    52
        }
    53
    54
        /// @inheritdoc IIncentiveCalculationContract
    55 0
        function calculateIncentive(
    56
            uint256[] memory allocationPreOperation,
    57
            uint256[] memory allocationPostOperation,
    58
            uint256[] memory allocationTarget,
    59
            uint256[] memory weights,
    60
            uint256 totalAllocationPreOperation,
    61
            uint256 totalAllocationPostOperation,
    62
            uint256 totalAllocationTarget,
    63
            uint256 energyToUSDExchangeRatio
    64
        )
    65
            public
    66
            pure
    67 0
            returns (int256 incentiveUSD, bool isSuccess)
    68
        {
    69 0
            if (
    70 0
                allocationPreOperation.length != allocationPostOperation.length
    71 0
                    || allocationPreOperation.length != allocationTarget.length
    72
            ) {
    73 0
                revert INVALID_ARRAY_LENGTH();
    74
            }
    75
    76 0
            uint256 energyBefore;
    77 0
            uint256 energyAfter;
    78 0
            bool _isSuccess;
    79
    80 0
            (energyBefore, _isSuccess) = energy(
    81 0
                allocationPreOperation, allocationTarget, weights, totalAllocationPreOperation, totalAllocationTarget
    82
            );
    83
    84 0
            if (!_isSuccess) {
    85 0
                return (0, false);
    86
            }
    87
    88 0
            (energyAfter, _isSuccess) = energy(
    89 0
                allocationPostOperation, allocationTarget, weights, totalAllocationPostOperation, totalAllocationTarget
    90
            );
    91
    92 0
            if (!_isSuccess) {
    93 0
                return (0, false);
    94
            }
    95
    96
            // Calculate energy difference first
    97 0
            int256 energyDiff = int256(energyBefore) - int256(energyAfter);
    98
    99
            // Handle positive and negative cases separately for safe multiplication
    100 0
            if (energyDiff >= 0) {
    101 0
                incentiveUSD = int256(Math.mulDiv(uint256(energyDiff), energyToUSDExchangeRatio, PRECISION));
    102
            } else {
    103 0
                incentiveUSD = -int256(Math.mulDiv(uint256(-energyDiff), energyToUSDExchangeRatio, PRECISION));
    104
            }
    105 0
            return (incentiveUSD, true);
    106
        }
    107
    }
    0% src/SuperAsset/IncentiveFundContract.sol
    Lines covered: 0 / 69 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
    5
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    6
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    7
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    8
    import { IIncentiveFundContract } from "../interfaces/SuperAsset/IIncentiveFundContract.sol";
    9
    import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
    10
    import { ISuperAsset } from "../interfaces/SuperAsset/ISuperAsset.sol";
    11
    import { ISuperAssetFactory } from "../interfaces/SuperAsset/ISuperAssetFactory.sol";
    12
    13
    /// @title Incentive Fund Contract
    14
    /// @author Superform Labs
    15
    /// @notice Manages incentive tokens in the SuperAsset system
    16
    /// @dev This contract is responsible for handling the incentive fund, including paying and taking incentives.
    17
    /// @dev For now it is OK to keep Access Control but it will be managed by SuperGovernor when ready, see
    18
    /// https://github.com/superform-xyz/v2-contracts/pull/377#discussion_r2058893391
    19 0
    contract IncentiveFundContract is IIncentiveFundContract {
    20
        using SafeERC20 for IERC20;
    21
    22
        // --- State Variables ---
    23 0
        address public tokenInIncentive;
    24 0
        address public tokenOutIncentive;
    25
    26 0
        ISuperAsset public superAsset;
    27 0
        ISuperGovernor public superGovernor;
    28
    29 0
        bool public incentivesActive;
    30
    31
        // --- Modifiers ---
    32 0
        modifier onlyManager() {
    33 0
            ISuperAssetFactory factory = ISuperAssetFactory(superGovernor.getAddress(superGovernor.SUPER_ASSET_FACTORY()));
    34 0
            address manager = factory.getIncentiveFundManager(address(superAsset));
    35 0
            if (msg.sender != manager) revert UNAUTHORIZED();
    36 0
            _;
    37
        }
    38
    39
        /// @inheritdoc IIncentiveFundContract
    40 0
        function initialize(
    41
            address _superGovernor,
    42
            address superAsset_,
    43
            address tokenInIncentive_,
    44
            address tokenOutIncentive_
    45
        )
    46
            external
    47
        {
    48 0
            if (_superGovernor == address(0)) revert ZERO_ADDRESS();
    49 0
            superGovernor = ISuperGovernor(_superGovernor);
    50
    51
            // Ensure this can only be called once
    52 0
            if (address(superAsset) != address(0)) revert ALREADY_INITIALIZED();
    53
    54 0
            if (superAsset_ == address(0)) revert ZERO_ADDRESS();
    55 0
            if (tokenInIncentive_ == address(0)) revert ZERO_ADDRESS();
    56 0
            if (tokenOutIncentive_ == address(0)) revert ZERO_ADDRESS();
    57
    58 0
            superAsset = ISuperAsset(superAsset_);
    59 0
            tokenInIncentive = tokenInIncentive_;
    60 0
            tokenOutIncentive = tokenOutIncentive_;
    61
        }
    62
    63
        /*//////////////////////////////////////////////////////////////
    64
                              EXTERNAL FUNCTIONS
    65
        //////////////////////////////////////////////////////////////*/
    66
        /// @inheritdoc IIncentiveFundContract
    67 0
        function setTokenInIncentive(address token) external onlyManager {
    68 0
            if (token == address(0)) revert ZERO_ADDRESS();
    69
    70 0
            bool isWhitelisted = superGovernor.isWhitelistedIncentiveToken(token);
    71
    72 0
            if (isWhitelisted) {
    73 0
                tokenInIncentive = token;
    74
            } else {
    75 0
                revert TOKEN_NOT_WHITELISTED();
    76
            }
    77
    78 0
            emit SettlementTokenInSet(token);
    79
        }
    80
    81
        /// @inheritdoc IIncentiveFundContract
    82 0
        function setTokenOutIncentive(address token) external onlyManager {
    83 0
            if (token == address(0)) revert ZERO_ADDRESS();
    84
    85 0
            bool isWhitelisted = superGovernor.isWhitelistedIncentiveToken(token);
    86
    87 0
            if (isWhitelisted) {
    88 0
                tokenOutIncentive = token;
    89
            } else {
    90
                revert TOKEN_NOT_WHITELISTED();
    91
            }
    92
    93
            emit SettlementTokenInSet(token);
    94
        }
    95
    96
        /// @inheritdoc IIncentiveFundContract
    97 0
        function toggleIncentives(bool enabled) external onlyManager {
    98 0
            incentivesActive = enabled;
    99 0
            emit IncentivesToggled(enabled);
    100
        }
    101
    102
        /// @inheritdoc IIncentiveFundContract
    103 0
        function payIncentive(address receiver, uint256 amountUSD) external onlyManager returns (uint256 amountToken) {
    104 0
            if (!incentivesActive) {
    105 0
                return 0;
    106
            }
    107
    108 0
            _validateInput(receiver, amountUSD);
    109 0
            if (tokenOutIncentive == address(0)) revert TOKEN_OUT_NOT_SET();
    110
    111
            // Get token price and check circuit breakers
    112 0
            (uint256 priceUSD,,,) = superAsset.getPriceAndCircuitBreakers(tokenOutIncentive);
    113
    114 0
            if (priceUSD > 0) {
    115
                // Convert USD amount to token amount using price
    116
                // amountToken = amountUSD / priceUSD
    117 0
                amountToken = Math.mulDiv(amountUSD, IERC20Metadata(tokenInIncentive).decimals(), priceUSD);
    118
    119 0
                IERC20(tokenOutIncentive).safeTransfer(receiver, amountToken);
    120 0
                emit IncentivePaid(receiver, tokenOutIncentive, amountToken);
    121
            }
    122
    123 0
            emit IncentivePaid(receiver, tokenOutIncentive, 0);
    124
            return 0;
    125
        }
    126
    127
        /// @inheritdoc IIncentiveFundContract
    128 0
        function takeIncentive(address sender, uint256 amountUSD) external onlyManager returns (uint256 amountToken) {
    129 0
            if (!incentivesActive) {
    130 0
                return 0;
    131
            }
    132
    133 0
            _validateInput(sender, amountUSD);
    134 0
            if (tokenInIncentive == address(0)) revert TOKEN_IN_NOT_SET();
    135
    136
            // Get token price and check circuit breakers
    137 0
            (uint256 priceUSD,,,) = superAsset.getPriceAndCircuitBreakers(tokenInIncentive);
    138
    139 0
            if (priceUSD > 0) {
    140
                // Convert USD amount to token amount using price
    141
                // amountToken = amountUSD / priceUSD
    142 0
                amountToken = Math.mulDiv(amountUSD, IERC20Metadata(tokenInIncentive).decimals(), priceUSD);
    143
    144 0
                IERC20(tokenInIncentive).safeTransferFrom(sender, address(this), amountToken);
    145 0
                emit IncentiveTaken(sender, tokenInIncentive, amountToken);
    146
            }
    147
    148 0
            emit IncentiveTaken(sender, tokenInIncentive, 0);
    149 0
            return 0;
    150
        }
    151
    152
        /// @inheritdoc IIncentiveFundContract
    153 0
        function withdraw(address receiver, address tokenOut, uint256 amount) external onlyManager {
    154 0
            _validateInput(receiver, amount);
    155 0
            if (tokenOut == address(0)) revert ZERO_ADDRESS();
    156
    157 0
            IERC20(tokenOut).safeTransfer(receiver, amount);
    158 0
            emit RebalanceWithdrawal(receiver, tokenOut, amount);
    159
        }
    160
    161
        /// @inheritdoc IIncentiveFundContract
    162 0
        function incentivesEnabled() external view returns (bool) {
    163 0
            return incentivesActive;
    164
        }
    165
    166
        /*//////////////////////////////////////////////////////////////
    167
                    INTERNAL FUNCTIONS
    168
        //////////////////////////////////////////////////////////////*/
    169 0
        function _validateInput(address user, uint256 amount) internal pure {
    170 0
            if (user == address(0)) revert ZERO_ADDRESS();
    171 0
            if (amount == 0) revert ZERO_AMOUNT();
    172
        }
    173
    }
    0% src/SuperAsset/SuperAsset.sol
    Lines covered: 0 / 494 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    5
    import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    6
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    7
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    8
    import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
    9
    import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    10
    11
    import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
    12
    import { ISuperAsset } from "../interfaces/SuperAsset/ISuperAsset.sol";
    13
    import { SuperAssetPriceLib } from "../libraries/SuperAssetPriceLib.sol";
    14
    import { ISuperAssetFactory } from "../interfaces/SuperAsset/ISuperAssetFactory.sol";
    15
    import { IIncentiveCalculationContract } from "../interfaces/SuperAsset/IIncentiveCalculationContract.sol";
    16
    import { IIncentiveFundContract } from "../interfaces/SuperAsset/IIncentiveFundContract.sol";
    17
    18
    /// @title SuperAsset
    19
    /// @author Superform Labs
    20
    /// @notice A meta-vault that manages deposits and redemptions across multiple underlying vaults.
    21
    /// @dev Implements ERC20 standard for better compatibility with integrators.
    22 0
    contract SuperAsset is ERC20, ISuperAsset {
    23
        using EnumerableSet for EnumerableSet.AddressSet;
    24
        using SafeERC20 for IERC20;
    25
        using Math for uint256;
    26
    27
        // --- Storage for ERC20 variables ---
    28
        string private tokenName;
    29
        string private tokenSymbol;
    30
    31
        // --- Interfaces ---
    32
        ISuperGovernor private superGovernor;
    33
        ISuperAssetFactory private factory;
    34
    35
        // --- Constants ---
    36 0
        uint256 private constant PRECISION = 1e18;
    37 0
        uint256 private constant DECIMALS = 18;
    38 0
        uint256 private constant DEPEG_LOWER_THRESHOLD = 98e16; // 0.98
    39 0
        uint256 private constant DEPEG_UPPER_THRESHOLD = 102e16; // 1.02
    40 0
        uint256 private constant DISPERSION_THRESHOLD = 1e16; // 1% relative standard deviation threshold
    41
    42
        // --- Fee Constants ---
    43 0
        uint256 public constant SWAP_FEE_PERC = 10 ** 6;
    44 0
        uint256 public constant MAX_SWAP_FEE_PERC = 10 ** 4; // Max 10% (1000 basis points)
    45
    46
        // --- State Variables ---
    47
        mapping(address token => TokenData data) private tokenData;
    48
    49
        // @notice Contains supported Vaults shares and standard ERC20s
    50
        EnumerableSet.AddressSet private _supportedAssets;
    51
    52 0
        uint256 public swapFeeInPercentage; // Swap fee as a percentage (e.g., 10 for 0.1%)
    53 0
        uint256 public swapFeeOutPercentage; // Swap fee as a percentage (e.g., 10 for 0.1%)
    54 0
        uint256 public energyToUSDExchangeRatio;
    55
    56
        // --- Addresses ---
    57 0
        address private constant USD = address(840);
    58 0
        address public primaryAsset;
    59
    60
        // SuperOracle related
    61
        bytes32 private constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
    62
    63
        /*//////////////////////////////////////////////////////////////
    64
                            CONTRACT INITIALIZATION
    65
        //////////////////////////////////////////////////////////////*/
    66 0
        constructor() ERC20("", "") { }
    67
    68
        /// @inheritdoc ISuperAsset
    69 0
        function initialize(
    70
            string memory name_,
    71
            string memory symbol_,
    72
            address asset,
    73
            address superGovernor_,
    74
            uint256 swapFeeInPercentage_,
    75
            uint256 swapFeeOutPercentage_
    76
        )
    77
            external
    78
        {
    79
            // Ensure this can only be called once
    80 0
            if (address(superGovernor) != address(0)) revert ALREADY_INITIALIZED();
    81
    82 0
            if (swapFeeInPercentage_ > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE();
    83 0
            if (swapFeeOutPercentage_ > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE();
    84
    85 0
            swapFeeInPercentage = swapFeeInPercentage_;
    86 0
            swapFeeOutPercentage = swapFeeOutPercentage_;
    87
    88
            // Initialize ERC20 name and symbol
    89 0
            tokenName = name_;
    90 0
            tokenSymbol = symbol_;
    91
    92 0
            primaryAsset = asset;
    93
    94 0
            superGovernor = ISuperGovernor(superGovernor_);
    95 0
            factory = ISuperAssetFactory(superGovernor.getAddress(superGovernor.SUPER_ASSET_FACTORY()));
    96
        }
    97
    98
        /*//////////////////////////////////////////////////////////////
    99
                                MANAGER FUNCTIONS
    100
        //////////////////////////////////////////////////////////////*/
    101
        /// @inheritdoc ISuperAsset
    102 0
        function whitelistERC20(address token) external {
    103 0
            _onlyManager();
    104 0
            if (token == address(0)) revert ZERO_ADDRESS();
    105 0
            if (tokenData[token].isSupportedERC20) revert ALREADY_WHITELISTED();
    106
    107
            // Set token as supported and active
    108 0
            tokenData[token].isSupportedERC20 = true;
    109 0
            tokenData[token].isActive = true;
    110
    111 0
            tokenData[token].oracle = superGovernor.getAddress(superGovernor.SUPER_ORACLE());
    112 0
            _supportedAssets.add(token);
    113
    114 0
            emit ERC20Whitelisted(token);
    115
        }
    116
    117
        /// @inheritdoc ISuperAsset
    118 0
        function removeERC20(address token) external {
    119 0
            _onlyManager();
    120 0
            if (token == address(0)) revert ZERO_ADDRESS();
    121
    122
            // Mark token as inactive
    123 0
            tokenData[token].isActive = false;
    124
    125
            // Prevent full purge if token has 0 balance
    126 0
            if (IERC20(token).balanceOf(address(this)) == 0) {
    127
                // Full removal - clear all data
    128 0
                _supportedAssets.remove(token);
    129 0
                tokenData[token].oracle = address(0);
    130 0
                tokenData[token].isSupportedERC20 = false;
    131
            }
    132
    133 0
            emit ERC20Removed(token);
    134
        }
    135
    136
        /// @inheritdoc ISuperAsset
    137 0
        function activateERC20(address token) external {
    138 0
            _onlyManager();
    139 0
            if (token == address(0)) revert ZERO_ADDRESS();
    140 0
            if (!tokenData[token].isSupportedERC20) revert TOKEN_NOT_SUPPORTED();
    141 0
            if (tokenData[token].isActive) revert TOKEN_ALREADY_ACTIVE();
    142
    143
            // Reactivate the token
    144 0
            tokenData[token].isActive = true;
    145
    146 0
            emit ERC20Activated(token);
    147
        }
    148
    149
        /// @inheritdoc ISuperAsset
    150 0
        function whitelistVault(address vault, address yieldSourceOracle) external {
    151 0
            _onlyManager();
    152 0
            if (vault == address(0) || yieldSourceOracle == address(0)) revert ZERO_ADDRESS();
    153 0
            if (tokenData[vault].isSupportedUnderlyingVault) revert ALREADY_WHITELISTED();
    154
    155
            // Set vault as supported and active
    156 0
            tokenData[vault].isSupportedUnderlyingVault = true;
    157 0
            tokenData[vault].isActive = true;
    158
    159 0
            tokenData[vault].oracle = yieldSourceOracle;
    160 0
            _supportedAssets.add(vault);
    161
    162 0
            emit VaultWhitelisted(vault);
    163
        }
    164
    165
        /// @inheritdoc ISuperAsset
    166 0
        function removeVault(address vault) external {
    167 0
            _onlyManager();
    168 0
            if (vault == address(0)) revert ZERO_ADDRESS();
    169
    170
            // Mark vault as inactive
    171 0
            tokenData[vault].isActive = false;
    172
    173
            // Prevent full purge if vault has 9 balance
    174 0
            if (IERC20(vault).balanceOf(address(this)) == 0) {
    175
                // Full removal - clear all data
    176 0
                _supportedAssets.remove(vault);
    177 0
                tokenData[vault].oracle = address(0);
    178 0
                tokenData[vault].isSupportedUnderlyingVault = false;
    179
            }
    180
    181 0
            emit VaultRemoved(vault);
    182
        }
    183
    184
        /// @inheritdoc ISuperAsset
    185 0
        function activateVault(address vault) external {
    186 0
            _onlyManager();
    187 0
            if (vault == address(0)) revert ZERO_ADDRESS();
    188 0
            if (!tokenData[vault].isSupportedUnderlyingVault) revert VAULT_NOT_SUPPORTED();
    189 0
            if (tokenData[vault].isActive) revert TOKEN_ALREADY_ACTIVE();
    190
    191
            // Reactivate the vault
    192 0
            tokenData[vault].isActive = true;
    193
    194 0
            emit VaultActivated(vault);
    195
        }
    196
    197
        /// @inheritdoc ISuperAsset
    198 0
        function setSwapFeeInPercentage(uint256 _feePercentage) external {
    199 0
            _onlyManager();
    200 0
            if (_feePercentage > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE();
    201 0
            swapFeeInPercentage = _feePercentage;
    202
        }
    203
    204
        /// @inheritdoc ISuperAsset
    205 0
        function setSwapFeeOutPercentage(uint256 _feePercentage) external {
    206 0
            _onlyManager();
    207 0
            if (_feePercentage > MAX_SWAP_FEE_PERC) revert INVALID_SWAP_FEE_PERCENTAGE();
    208 0
            swapFeeOutPercentage = _feePercentage;
    209
        }
    210
    211
        /// @inheritdoc ISuperAsset
    212 0
        function setWeight(address vault, uint256 weight) external {
    213 0
            _onlyManager();
    214 0
            if (vault == address(0)) revert ZERO_ADDRESS();
    215 0
            if (!tokenData[vault].isSupportedUnderlyingVault && !tokenData[vault].isSupportedERC20) revert NOT_SUPPORTED_TOKEN();
    216 0
            tokenData[vault].weights = weight;
    217 0
            emit WeightSet(vault, weight);
    218
        }
    219
    220
        /// @inheritdoc ISuperAsset
    221 0
        function setEnergyToUSDExchangeRatio(uint256 newRatio) external {
    222 0
            _onlyManager();
    223 0
            energyToUSDExchangeRatio = newRatio;
    224 0
            emit EnergyToUSDExchangeRatioSet(newRatio);
    225
        }
    226
    227
        /// @inheritdoc ISuperAsset
    228 0
        function mint(address to, uint256 amount) external {
    229 0
            _onlyManager();
    230 0
            _mint(to, amount);
    231
        }
    232
    233
        /// @inheritdoc ISuperAsset
    234 0
        function burn(address from, uint256 amount) external {
    235 0
            _onlyManager();
    236 0
            _burn(from, amount);
    237
        }
    238
    239
        /*//////////////////////////////////////////////////////////////
    240
                              STRATEGIST FUNCTIONS
    241
        //////////////////////////////////////////////////////////////*/
    242
        /// @inheritdoc ISuperAsset
    243 0
        function setTargetAllocations(address[] calldata tokens, uint256[] calldata allocations) external {
    244 0
            _onlyStrategist();
    245 0
            uint256 lenTokens = tokens.length;
    246 0
            if (lenTokens != allocations.length) revert INVALID_INPUT();
    247
    248 0
            for (uint256 i; i < lenTokens; i++) {
    249 0
                if (tokens[i] == address(0)) revert ZERO_ADDRESS();
    250 0
                if (!tokenData[tokens[i]].isSupportedUnderlyingVault && !tokenData[tokens[i]].isSupportedERC20) {
    251 0
                    revert NOT_SUPPORTED_TOKEN();
    252
                }
    253
            }
    254
    255 0
            for (uint256 i; i < lenTokens; i++) {
    256 0
                tokenData[tokens[i]].targetAllocations = allocations[i];
    257 0
                emit TargetAllocationSet(tokens[i], allocations[i]);
    258
            }
    259
        }
    260
    261
        /// @inheritdoc ISuperAsset
    262 0
        function setTargetAllocation(address token, uint256 allocation) external {
    263 0
            _onlyStrategist();
    264 0
            if (token == address(0)) revert ZERO_ADDRESS();
    265 0
            if (!tokenData[token].isSupportedUnderlyingVault && !tokenData[token].isSupportedERC20) {
    266 0
                revert NOT_SUPPORTED_TOKEN();
    267
            }
    268
    269
            // @dev Allocations get normalized inside the ICC so we dont need to additional checks here
    270 0
            tokenData[token].targetAllocations = allocation;
    271 0
            emit TargetAllocationSet(token, allocation);
    272
        }
    273
    274
        /*//////////////////////////////////////////////////////////////
    275
                                EXTERNAL FUNCTIONS
    276
        //////////////////////////////////////////////////////////////*/
    277
        /// @inheritdoc ISuperAsset
    278 0
        function deposit(DepositArgs memory args) public returns (DepositReturnVars memory ret) {
    279
            // First all the non state changing functions
    280 0
            if (args.receiver == address(0) || args.tokenIn == address(0)) revert ZERO_ADDRESS();
    281 0
            if (args.amountTokenToDeposit == 0) revert ZERO_AMOUNT();
    282 0
            if (
    283 0
                !tokenData[args.tokenIn].isSupportedERC20 && !tokenData[args.tokenIn].isSupportedUnderlyingVault
    284 0
                    || !tokenData[args.tokenIn].isActive
    285
            ) {
    286 0
                revert NOT_SUPPORTED_TOKEN();
    287
            }
    288
    289
            // Create preview deposit args
    290 0
            PreviewDepositArgs memory previewArgs = PreviewDepositArgs({
    291 0
                tokenIn: args.tokenIn,
    292 0
                amountTokenToDeposit: args.amountTokenToDeposit,
    293
                isSoft: false
    294
            });
    295
    296
            // Call previewDeposit with the new struct approach
    297 0
            PreviewDepositReturnVars memory previewRet = previewDeposit(previewArgs);
    298
    299
            // Store results in return variable
    300 0
            ret.amountSharesMinted = previewRet.amountSharesMinted;
    301 0
            ret.swapFee = previewRet.swapFee;
    302 0
            ret.amountIncentiveUSDDeposit = previewRet.amountIncentiveUSDDeposit;
    303
    304
            // incentiveCalculationSuccess == false but totalSupply == 0 for the first deposit, this check allows for the
    305
            // first deposit (in which case calculateIncentive() will fail) to pass
    306 0
            if (!previewRet.incentiveCalculationSuccess && totalSupply() != 0) revert INCENTIVE_CALCULATION_FAILED();
    307
    308
            // Slippage Check
    309 0
            if (ret.amountSharesMinted < args.minSharesOut) revert SLIPPAGE_PROTECTION();
    310
    311 0
            if (previewRet.assetWithBreakerTriggered != address(0)) {
    312
                // Circuit Breaker Checks
    313 0
                if (previewRet.isDepeg) {
    314 0
                    revert SUPPORTED_ASSET_PRICE_DEPEG(previewRet.assetWithBreakerTriggered);
    315
                }
    316 0
                if (previewRet.isOracleOff) {
    317 0
                    revert SUPPORTED_ASSET_PRICE_ORACLE_OFF(previewRet.assetWithBreakerTriggered);
    318
                }
    319 0
                if (previewRet.isDispersion) {
    320 0
                    revert SUPPORTED_ASSET_PRICE_DISPERSION(previewRet.assetWithBreakerTriggered);
    321
                }
    322 0
                if (previewRet.oraclePriceUSD == 0) {
    323 0
                    revert SUPPORTED_ASSET_PRICE_ZERO(previewRet.assetWithBreakerTriggered);
    324
                }
    325
            }
    326
    327 0
            if (!previewRet.tokenInFound) {
    328 0
                revert NOT_SUPPORTED_TOKEN();
    329
            }
    330
    331
            // Settle Incentives
    332 0
            if (ret.amountIncentiveUSDDeposit > 0) {
    333 0
                _settleIncentive(args.receiver, ret.amountIncentiveUSDDeposit);
    334
            }
    335
    336
            // Transfer the tokenIn from the sender to this contract
    337 0
            IERC20(args.tokenIn).safeTransferFrom(msg.sender, address(this), args.amountTokenToDeposit);
    338
    339
            // Transfer swap fees to SuperBank
    340 0
            IERC20(args.tokenIn).safeTransfer(superGovernor.getAddress(superGovernor.SUPER_BANK()), ret.swapFee);
    341
            // Mint SuperUSD shares
    342 0
            _mint(args.receiver, ret.amountSharesMinted);
    343
    344 0
            emit Deposit(
    345 0
                args.receiver,
    346 0
                args.tokenIn,
    347 0
                args.amountTokenToDeposit,
    348 0
                ret.amountSharesMinted,
    349 0
                ret.swapFee,
    350 0
                ret.amountIncentiveUSDDeposit
    351
            );
    352
        }
    353
    354
    355
        /// @inheritdoc ISuperAsset
    356 0
        function redeem(RedeemArgs memory args) public returns (RedeemReturnVars memory ret) {
    357
            // First validate parameters
    358 0
            if (args.receiver == address(0) || args.tokenOut == address(0)) revert ZERO_ADDRESS();
    359 0
            if (args.amountSharesToRedeem == 0) revert ZERO_AMOUNT();
    360
    361
            // NOTE: Only revert if the token was not in the whitelist even before 
    362 0
            if (!_supportedAssets.contains(args.tokenOut)) revert NOT_SUPPORTED_TOKEN();
    363
    364
            // Create preview redeem args
    365 0
            PreviewRedeemArgs memory previewArgs = PreviewRedeemArgs({
    366 0
                tokenOut: args.tokenOut,
    367 0
                amountSharesToRedeem: args.amountSharesToRedeem,
    368
                // NOTE: Here we set isSoft=true on purpose since the desired behavior is to make the redeem() flow not to revert even in case of circtuit breakers triggered
    369
                // The reason is the redeem() allows SuperAsset to sell assets and if an asset has circuit breakers triggered then it's likely an asset whose risk profile does not match the kind of risk that we desire in the SuperAsset balance sheet
    370
                // This can be considered opinionated and an argument could be it should be the SuperAsset manager making decision on that, we think it can be discussed
    371 0
                isSoft: true // isSoft = true for soft checks that won't revert on circuit breaker triggers
    372
             });
    373
    374
            // Call previewRedeem with the new struct approach
    375 0
            PreviewRedeemReturnVars memory previewRet = previewRedeem(previewArgs);
    376
    377
            // Store results in return variable
    378 0
            ret.amountTokenOutAfterFees = previewRet.amountTokenOutAfterFees;
    379 0
            ret.swapFee = previewRet.swapFee;
    380 0
            ret.amountIncentiveUSDRedeem = previewRet.amountIncentiveUSDRedeem;
    381
    382
            // --- Post-preview checks ---
    383
            // incentiveCalculationSuccess == false but totalSupply == 0 for the first deposit, this check allows for the
    384
            // first deposit (in which case calculateIncentive() will fail) to pass. Similar logic applies to redeem.
    385 0
            if (!previewRet.incentiveCalculationSuccess && totalSupply() != 0) revert INCENTIVE_CALCULATION_FAILED();
    386
    387
            // Slippage Check
    388 0
            if (ret.amountTokenOutAfterFees < args.minTokenOut) revert SLIPPAGE_PROTECTION();
    389
    390
            // Circuit Breaker Checks
    391 0
            if (previewRet.assetWithBreakerTriggered != address(0)) {
    392 0
                if (previewRet.isDepeg) {
    393 0
                    revert SUPPORTED_ASSET_PRICE_DEPEG(previewRet.assetWithBreakerTriggered);
    394
                }
    395 0
                if (previewRet.isOracleOff) {
    396 0
                    revert SUPPORTED_ASSET_PRICE_ORACLE_OFF(previewRet.assetWithBreakerTriggered);
    397
                }
    398 0
                if (previewRet.isDispersion) {
    399 0
                    revert SUPPORTED_ASSET_PRICE_DISPERSION(previewRet.assetWithBreakerTriggered);
    400
                }
    401 0
                if (previewRet.oraclePriceUSD == 0) {
    402 0
                    revert SUPPORTED_ASSET_PRICE_ZERO(previewRet.assetWithBreakerTriggered);
    403
                }
    404
            }
    405
    406 0
            if (!previewRet.tokenOutFound) {
    407 0
                revert NOT_SUPPORTED_TOKEN(); // Or a more specific error if tokenOut_ was expected to be found by preview
    408
            }
    409
    410
            // --- State Changing Operations ---
    411
    412
            // Settle Incentives
    413 0
            if (ret.amountIncentiveUSDRedeem > 0) {
    414 0
                _settleIncentive(args.receiver, ret.amountIncentiveUSDRedeem);
    415
            }
    416
            // Burn SuperUSD shares from the sender
    417 0
            _burn(msg.sender, args.amountSharesToRedeem);
    418
    419
            // Transfer swap fees to SuperBank
    420 0
            IERC20(args.tokenOut).safeTransfer(superGovernor.getAddress(superGovernor.SUPER_BANK()), ret.swapFee);
    421
    422
            // Transfer assets to receiver
    423 0
            IERC20(args.tokenOut).safeTransfer(args.receiver, ret.amountTokenOutAfterFees);
    424
    425
            // --- Emit event and set return values ---
    426 0
            emit Redeem(
    427 0
                args.receiver,
    428 0
                args.tokenOut,
    429 0
                args.amountSharesToRedeem,
    430 0
                ret.amountTokenOutAfterFees,
    431 0
                ret.swapFee,
    432 0
                ret.amountIncentiveUSDRedeem
    433
            );
    434
        }
    435
    436
        /// @inheritdoc ISuperAsset
    437 0
        function swap(SwapArgs memory args) external returns (SwapReturnVars memory ret) {
    438 0
            if (args.receiver == address(0) || args.tokenIn == address(0) || args.tokenOut == address(0)) {
    439 0
                revert ZERO_ADDRESS();
    440
            }
    441
    442
            // Create deposit args from swap args
    443 0
            DepositArgs memory depositArgs = DepositArgs({
    444 0
                receiver: msg.sender,
    445 0
                tokenIn: args.tokenIn,
    446 0
                amountTokenToDeposit: args.amountTokenToDeposit,
    447
                minSharesOut: 0
    448
            });
    449 0
            DepositReturnVars memory depositRet = deposit(depositArgs);
    450
    451
            // Create redeem args from swap args and deposit result
    452 0
            RedeemArgs memory redeemArgs = RedeemArgs({
    453 0
                receiver: args.receiver,
    454 0
                amountSharesToRedeem: depositRet.amountSharesMinted,
    455 0
                tokenOut: args.tokenOut,
    456 0
                minTokenOut: args.minTokenOut
    457
            });
    458 0
            RedeemReturnVars memory redeemRet = redeem(redeemArgs);
    459
    460
            // Fill the return struct
    461 0
            ret.amountSharesIntermediateStep = depositRet.amountSharesMinted;
    462 0
            ret.amountTokenOutAfterFees = redeemRet.amountTokenOutAfterFees;
    463 0
            ret.swapFeeIn = depositRet.swapFee;
    464 0
            ret.swapFeeOut = redeemRet.swapFee;
    465 0
            ret.amountIncentivesIn = depositRet.amountIncentiveUSDDeposit;
    466 0
            ret.amountIncentivesOut = redeemRet.amountIncentiveUSDRedeem;
    467
    468 0
            emit Swap(
    469 0
                args.receiver,
    470 0
                args.tokenIn,
    471 0
                args.amountTokenToDeposit,
    472 0
                args.tokenOut,
    473 0
                ret.amountSharesIntermediateStep,
    474 0
                ret.amountTokenOutAfterFees,
    475 0
                ret.swapFeeIn,
    476 0
                ret.swapFeeOut,
    477 0
                ret.amountIncentivesIn,
    478 0
                ret.amountIncentivesOut
    479
            );
    480
        }
    481
    482
        /*//////////////////////////////////////////////////////////////
    483
                                PREVIEW FUNCTIONS
    484
        //////////////////////////////////////////////////////////////*/
    485
        /// @inheritdoc ISuperAsset
    486 0
        function previewDeposit(PreviewDepositArgs memory args) public view returns (PreviewDepositReturnVars memory ret) {
    487
            // Calculate swap fees
    488 0
            ret.swapFee = Math.mulDiv(args.amountTokenToDeposit, swapFeeInPercentage, SWAP_FEE_PERC);
    489
    490
            // Get current and post-operation allocations using the struct-based return value
    491 0
            ISuperAsset.AllocationOperationReturnVars memory allocRet = getAllocationsPrePostOperationDeposit(
    492 0
                args.tokenIn, args.amountTokenToDeposit, args.amountTokenToDeposit - ret.swapFee, args.isSoft
    493
            );
    494
    495
            // Copy values from allocation return struct to our local variable
    496 0
            ret.amountSharesMinted = allocRet.amountAssets;
    497
    498
            // Copy circuit breaker info to return struct
    499 0
            ret.assetWithBreakerTriggered = allocRet.assetWithBreakerTriggered;
    500 0
            ret.oraclePriceUSD = allocRet.oraclePriceUSD;
    501 0
            ret.isDepeg = allocRet.isDepeg;
    502 0
            ret.isDispersion = allocRet.isDispersion;
    503 0
            ret.isOracleOff = allocRet.isOracleOff;
    504 0
            ret.tokenInFound = allocRet.tokenFound;
    505
    506 0
            if ((ret.isDepeg || ret.isDispersion || ret.isOracleOff || ret.oraclePriceUSD == 0)) {
    507
                // Early return with empty result but with circuit breaker info
    508 0
                ret.amountSharesMinted = 0;
    509 0
                ret.swapFee = 0;
    510 0
                ret.amountIncentiveUSDDeposit = 0;
    511 0
                ret.incentiveCalculationSuccess = false;
    512
    513
                return ret;
    514
            }
    515
    516
            // Calculate incentives (via ICC)
    517 0
            if (IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).incentivesEnabled()) {
    518
                (ret.amountIncentiveUSDDeposit, ret.incentiveCalculationSuccess) = IIncentiveCalculationContract(
    519
                    factory.getIncentiveCalculationContract(address(this))
    520
                ).calculateIncentive(
    521
                    allocRet.absoluteAllocationPreOperation,
    522
                    allocRet.absoluteAllocationPostOperation,
    523
                    allocRet.absoluteTargetAllocation,
    524
                    allocRet.vaultWeights,
    525
                    allocRet.totalAllocationPreOperation,
    526
                    allocRet.totalAllocationPostOperation,
    527
                    allocRet.totalTargetAllocation,
    528
                    energyToUSDExchangeRatio
    529
                );
    530
            } else {
    531
                // if incentives disabled, soft return incentiveCalculationSuccess as true
    532
                ret.incentiveCalculationSuccess = true;
    533
            }
    534
        }
    535
    536
        /// @inheritdoc ISuperAsset
    537 0
        function previewRedeem(PreviewRedeemArgs memory args) public view returns (PreviewRedeemReturnVars memory ret) {
    538
            // Get current and post-operation allocations using the struct-based return value
    539 0
            ISuperAsset.AllocationOperationReturnVars memory allocRet =
    540 0
                getAllocationsPrePostOperationRedeem(args.tokenOut, args.amountSharesToRedeem, args.isSoft);
    541
    542
            // Copy circuit breaker info to return struct
    543 0
            ret.assetWithBreakerTriggered = allocRet.assetWithBreakerTriggered;
    544 0
            ret.oraclePriceUSD = allocRet.oraclePriceUSD;
    545 0
            ret.isDepeg = allocRet.isDepeg;
    546 0
            ret.isDispersion = allocRet.isDispersion;
    547 0
            ret.isOracleOff = allocRet.isOracleOff;
    548 0
            ret.tokenOutFound = allocRet.tokenFound;
    549
    550 0
            if ((ret.isDepeg || ret.isDispersion || ret.isOracleOff || ret.oraclePriceUSD == 0)) {
    551
                // Early return with empty result but with circuit breaker info
    552 0
                ret.amountTokenOutAfterFees = 0;
    553 0
                ret.swapFee = 0;
    554 0
                ret.amountIncentiveUSDRedeem = 0;
    555 0
                ret.incentiveCalculationSuccess = false;
    556
    557
                return ret;
    558
            }
    559
    560
            // Calculate swap fee
    561 0
            ret.swapFee = Math.mulDiv(allocRet.amountAssets, swapFeeOutPercentage, SWAP_FEE_PERC); // 0.1%
    562 0
            ret.amountTokenOutAfterFees = allocRet.amountAssets - ret.swapFee;
    563
    564
            // Calculate incentives (via ICC)
    565 0
            if (IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).incentivesEnabled()) {
    566 0
                (ret.amountIncentiveUSDRedeem, ret.incentiveCalculationSuccess) = IIncentiveCalculationContract(
    567 0
                    factory.getIncentiveCalculationContract(address(this))
    568
                ).calculateIncentive(
    569 0
                    allocRet.absoluteAllocationPreOperation,
    570 0
                    allocRet.absoluteAllocationPostOperation,
    571 0
                    allocRet.absoluteTargetAllocation,
    572 0
                    allocRet.vaultWeights,
    573 0
                    allocRet.totalAllocationPreOperation,
    574 0
                    allocRet.totalAllocationPostOperation,
    575 0
                    allocRet.totalTargetAllocation,
    576 0
                    energyToUSDExchangeRatio
    577
                );
    578
            } else {
    579
                // if incentives disabled, soft return incentiveCalculationSuccess as true
    580 0
                ret.incentiveCalculationSuccess = true;
    581
            }
    582
        }
    583
    584
        /// @inheritdoc ISuperAsset
    585 0
        function previewSwap(PreviewSwapArgs memory args) external view returns (PreviewSwapReturnVars memory ret) {
    586 0
            uint256 amountSharesMinted;
    587
            // Create preview deposit args
    588 0
            PreviewDepositArgs memory depositArgs = PreviewDepositArgs({
    589 0
                tokenIn: args.tokenIn,
    590 0
                amountTokenToDeposit: args.amountTokenToDeposit,
    591 0
                isSoft: args.isSoft
    592
            });
    593
    594
            // Call previewDeposit with the new struct approach
    595 0
            PreviewDepositReturnVars memory depositRet = previewDeposit(depositArgs);
    596
    597
            // Store results
    598 0
            amountSharesMinted = depositRet.amountSharesMinted;
    599 0
            ret.swapFeeIn = depositRet.swapFee;
    600 0
            ret.amountIncentiveUSDDeposit = depositRet.amountIncentiveUSDDeposit;
    601 0
            ret.assetWithBreakerTriggered = depositRet.assetWithBreakerTriggered;
    602 0
            ret.oraclePriceUSD = depositRet.oraclePriceUSD;
    603 0
            ret.isDepeg = depositRet.isDepeg;
    604 0
            ret.isDispersion = depositRet.isDispersion;
    605 0
            ret.isOracleOff = depositRet.isOracleOff;
    606 0
            ret.tokenInFound = depositRet.tokenInFound;
    607 0
            ret.incentiveCalculationSuccess = depositRet.incentiveCalculationSuccess;
    608
    609 0
            if (
    610 0
                !ret.tokenInFound || ret.isDepeg || ret.isDispersion || ret.isOracleOff || !ret.incentiveCalculationSuccess
    611 0
                    || ret.oraclePriceUSD == 0
    612
            ) {
    613
                // Return empty result but with circuit breaker info
    614 0
                ret.amountTokenOutAfterFees = 0;
    615 0
                ret.swapFeeOut = 0;
    616 0
                ret.amountIncentiveUSDRedeem = 0;
    617
                return ret;
    618
            }
    619
    620
            // note: it is possible to optimize the calls to oracle if the logic is extracted inside the preview functions
    621
    622
            // Create preview redeem args
    623 0
            PreviewRedeemArgs memory redeemArgs = PreviewRedeemArgs({
    624 0
                tokenOut: args.tokenOut,
    625 0
                amountSharesToRedeem: amountSharesMinted,
    626 0
                isSoft: args.isSoft
    627
            });
    628
    629
            // Call previewRedeem with the new struct approach
    630 0
            PreviewRedeemReturnVars memory redeemRet = previewRedeem(redeemArgs);
    631
    632
            // Store results
    633 0
            ret.amountTokenOutAfterFees = redeemRet.amountTokenOutAfterFees;
    634 0
            ret.swapFeeOut = redeemRet.swapFee;
    635 0
            ret.amountIncentiveUSDRedeem = redeemRet.amountIncentiveUSDRedeem;
    636 0
            ret.assetWithBreakerTriggered = redeemRet.assetWithBreakerTriggered;
    637 0
            ret.oraclePriceUSD = redeemRet.oraclePriceUSD;
    638 0
            ret.isDepeg = redeemRet.isDepeg;
    639 0
            ret.isDispersion = redeemRet.isDispersion;
    640 0
            ret.isOracleOff = redeemRet.isOracleOff;
    641 0
            ret.tokenInFound = redeemRet.tokenOutFound; // Mapped from tokenOutFound
    642 0
            ret.incentiveCalculationSuccess = redeemRet.incentiveCalculationSuccess;
    643
        }
    644
    645
        /*//////////////////////////////////////////////////////////////
    646
                            EXTERNAL GETTER FUNCTIONS
    647
        //////////////////////////////////////////////////////////////*/
    648
        /// @inheritdoc ISuperAsset
    649 0
        function getTokenData(address token) external view returns (TokenData memory) {
    650 0
            return tokenData[token];
    651
        }
    652
    653
        /// @inheritdoc ISuperAsset
    654 0
        function getPrecision() external pure returns (uint256) {
    655
            return PRECISION;
    656
        }
    657
    658
        /*//////////////////////////////////////////////////////////////
    659
                            PUBLIC GETTER FUNCTIONS
    660
        //////////////////////////////////////////////////////////////*/
    661
        /// @inheritdoc ISuperAsset
    662 0
        function getPriceAndCircuitBreakers(address token)
    663
            public
    664
            view
    665 0
            returns (uint256 priceUSD, bool isDepeg, bool isDispersion, bool isOracleOff)
    666
        {
    667 0
            address superOracle = superGovernor.getAddress(superGovernor.SUPER_ORACLE());
    668
    669 0
            return SuperAssetPriceLib.getPriceWithCircuitBreakers(
    670 0
                ISuperAsset.PriceArgs({
    671
                    superOracle: superOracle,
    672 0
                    superAsset: address(this),
    673
                    token: token,
    674
                    usd: USD,
    675
                    depegLowerThreshold: DEPEG_LOWER_THRESHOLD,
    676
                    depegUpperThreshold: DEPEG_UPPER_THRESHOLD,
    677
                    dispersionThreshold: DISPERSION_THRESHOLD
    678
                })
    679
            );
    680
        }
    681
    682
        /// @inheritdoc ISuperAsset
    683 0
        function getSuperAssetPPS()
    684
            external
    685
            view
    686
            returns (
    687 0
                address[] memory activeTokens,
    688 0
                uint256[] memory pricePerTokenUSD,
    689 0
                bool[] memory isDepeg,
    690 0
                bool[] memory isDispersion,
    691 0
                bool[] memory isOracleOff,
    692 0
                uint256 pps
    693
            )
    694 0
        {
    695 0
            uint256 len = _supportedAssets.length();
    696 0
            activeTokens = new address[](len);
    697 0
            pricePerTokenUSD = new uint256[](len);
    698 0
            isDepeg = new bool[](len);
    699 0
            isDispersion = new bool[](len);
    700 0
            isOracleOff = new bool[](len);
    701
    702 0
            uint256 totalValueUSD;
    703 0
            for (uint256 i; i < len; i++) {
    704 0
                address token = _supportedAssets.at(i);
    705 0
                activeTokens[i] = token;
    706
    707 0
                (uint256 priceUSD, bool isTokenDepeg, bool isTokenDispersion, bool isTokenOracleOff) =
    708 0
                    getPriceAndCircuitBreakers(token);
    709
    710 0
                pricePerTokenUSD[i] = priceUSD;
    711 0
                isDepeg[i] = isTokenDepeg;
    712 0
                isDispersion[i] = isTokenDispersion;
    713 0
                isOracleOff[i] = isTokenOracleOff;
    714
    715 0
                uint256 balance = IERC20(token).balanceOf(address(this));
    716
    717 0
                if (balance > 0) {
    718 0
                    totalValueUSD += Math.mulDiv(balance, priceUSD, 10 ** IERC20Metadata(token).decimals());
    719
                }
    720
            }
    721
    722 0
            uint256 totalSupply_ = totalSupply();
    723
            // PPS = Total Value in USD / Total Supply, normalized to PRECISION
    724 0
            if (totalSupply_ == 0) {
    725 0
                pps = PRECISION;
    726
            } else {
    727 0
                pps = Math.mulDiv(totalValueUSD, PRECISION, totalSupply_);
    728
            }
    729
        }
    730
    731
        /// @inheritdoc ISuperAsset
    732 0
        function getAllocations()
    733
            external
    734
            view
    735
            returns (
    736 0
                uint256[] memory absoluteCurrentAllocation,
    737 0
                uint256 totalCurrentAllocation,
    738 0
                uint256[] memory absoluteTargetAllocation,
    739 0
                uint256 totalTargetAllocation
    740
            )
    741 0
        {
    742 0
            uint256 length = _supportedAssets.length();
    743 0
            absoluteCurrentAllocation = new uint256[](length);
    744 0
            absoluteTargetAllocation = new uint256[](length);
    745 0
            for (uint256 i; i < length; i++) {
    746 0
                address vault = _supportedAssets.at(i);
    747 0
                absoluteCurrentAllocation[i] = IERC20(vault).balanceOf(address(this));
    748 0
                totalCurrentAllocation += absoluteCurrentAllocation[i];
    749 0
                absoluteTargetAllocation[i] = tokenData[vault].targetAllocations;
    750 0
                totalTargetAllocation += absoluteTargetAllocation[i];
    751
            }
    752
        }
    753
    754
        /// @inheritdoc ISuperAsset
    755 0
        function getAllocationsPrePostOperationDeposit(
    756
            address token,
    757
            uint256 deltaToken,
    758
            uint256 amountToken,
    759
            bool isSoft
    760
        )
    761
            public
    762
            view
    763 0
            returns (ISuperAsset.AllocationOperationReturnVars memory ret)
    764 0
        {
    765 0
            GetAllocationsPrePostOperationsDeposit memory s;
    766
    767 0
            s.extendedLength = _supportedAssets.length();
    768
            // Initialize the arrays in the return struct
    769 0
            ret.absoluteAllocationPreOperation = new uint256[](s.extendedLength);
    770 0
            ret.absoluteAllocationPostOperation = new uint256[](s.extendedLength);
    771 0
            ret.absoluteTargetAllocation = new uint256[](s.extendedLength);
    772 0
            ret.vaultWeights = new uint256[](s.extendedLength);
    773 0
            s.totalValueUSD = 0;
    774 0
            s.priceUSDToken = 0;
    775
    776 0
            for (uint256 i; i < s.extendedLength; i++) {
    777 0
                s.token = _supportedAssets.at(i);
    778 0
                (ret.oraclePriceUSD, ret.isDepeg, ret.isDispersion, ret.isOracleOff) = getPriceAndCircuitBreakers(s.token);
    779
    780 0
                if (!isSoft && (ret.isDepeg || ret.isDispersion || ret.isOracleOff || ret.oraclePriceUSD == 0)) {
    781
                    // Return early with circuit breaker information
    782 0
                    ret.assetWithBreakerTriggered = s.token;
    783 0
                    return ret;
    784
                }
    785
    786 0
                s.balance = IERC20(s.token).balanceOf(address(this));
    787 0
                uint256 decimals = IERC20Metadata(s.token).decimals();
    788 0
                if (s.balance > 0) {
    789 0
                    s.totalValueUSD += Math.mulDiv(s.balance, ret.oraclePriceUSD, 10 ** decimals);
    790
                }
    791
                // Convert balance to USD value using price
    792 0
                ret.absoluteAllocationPreOperation[i] = Math.mulDiv(s.balance, ret.oraclePriceUSD, 10 ** decimals);
    793 0
                ret.totalAllocationPreOperation += ret.absoluteAllocationPreOperation[i];
    794 0
                ret.absoluteAllocationPostOperation[i] = ret.absoluteAllocationPreOperation[i];
    795 0
                if (s.token == token) {
    796 0
                    s.priceUSDToken = ret.oraclePriceUSD;
    797 0
                    ret.tokenFound = true;
    798 0
                    s.absDeltaValue = Math.mulDiv(deltaToken, s.priceUSDToken, 10 ** decimals);
    799 0
                    s.deltaValue = int256(s.absDeltaValue);
    800 0
                    ret.absoluteAllocationPostOperation[i] =
    801 0
                        uint256(int256(ret.absoluteAllocationPreOperation[i]) + s.deltaValue);
    802
                }
    803 0
                ret.totalAllocationPostOperation += ret.absoluteAllocationPostOperation[i];
    804 0
                ret.absoluteTargetAllocation[i] = tokenData[s.token].targetAllocations;
    805 0
                ret.totalTargetAllocation += ret.absoluteTargetAllocation[i];
    806 0
                ret.vaultWeights[i] = tokenData[s.token].weights;
    807
            }
    808
    809 0
            uint256 superAssetPPS;
    810 0
            uint256 totalSupply_ = totalSupply();
    811
            // PPS = Total Value in USD / Total Supply, normalized to PRECISION
    812 0
            if (totalSupply_ == 0) {
    813 0
                superAssetPPS = PRECISION;
    814
            } else {
    815 0
                superAssetPPS = Math.mulDiv(s.totalValueUSD, PRECISION, totalSupply_);
    816
            }
    817
    818 0
            ret.amountAssets = Math.mulDiv(amountToken, s.priceUSDToken, superAssetPPS);
    819
    820 0
            uint8 decimalsToken = IERC20Metadata(token).decimals();
    821
    822
            // Adjust for decimals
    823 0
            if (decimalsToken < DECIMALS) {
    824 0
                ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (DECIMALS - decimalsToken), PRECISION);
    825 0
            } else if (decimalsToken > DECIMALS) {
    826 0
                ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (decimalsToken - DECIMALS), PRECISION);
    827
            }
    828
        }
    829
    830
        /// @inheritdoc ISuperAsset
    831 0
        function getAllocationsPrePostOperationRedeem(
    832
            address token,
    833
            uint256 amountToken,
    834
            bool isSoft
    835
        )
    836
            public
    837
            view
    838 0
            returns (ISuperAsset.AllocationOperationReturnVars memory ret)
    839 0
        {
    840
            // 1. if deposit, deltaToken is amountTokenToDeposit (that the user sent)
    841
            // 2. however, if redeem, all prices are fetched first (priceUSD of token out and superAsset PPS)
    842
            // 2.1 then, these prices are used to calculate amountTokenOutBeforeFees, which is the delta token
    843 0
            GetAllocationsPrePostOperationsRedeem memory s;
    844
    845 0
            s.extendedLength = _supportedAssets.length();
    846 0
            s.oraclePriceUSDs = new uint256[](s.extendedLength);
    847 0
            s.balances = new uint256[](s.extendedLength);
    848 0
            s.decimals = new uint256[](s.extendedLength);
    849 0
            s.isDepegs = new bool[](s.extendedLength);
    850 0
            s.isDispersions = new bool[](s.extendedLength);
    851 0
            s.isOracleOffs = new bool[](s.extendedLength);
    852 0
            s.totalValueUSD = 0;
    853 0
            s.priceUSDToken = 0;
    854
    855 0
            for (uint256 i; i < s.extendedLength; i++) {
    856 0
                s.token = _supportedAssets.at(i);
    857 0
                (s.oraclePriceUSDs[i], s.isDepegs[i], s.isDispersions[i], s.isOracleOffs[i]) =
    858 0
                    getPriceAndCircuitBreakers(s.token);
    859
    860 0
                if (!isSoft && (s.isDepegs[i] || s.isDispersions[i] || s.isOracleOffs[i] || s.oraclePriceUSDs[i] == 0)) {
    861
                    // Return early with circuit breaker information
    862 0
                    ret.assetWithBreakerTriggered = s.token;
    863 0
                    ret.oraclePriceUSD = s.oraclePriceUSDs[i];
    864 0
                    ret.isDepeg = s.isDepegs[i];
    865 0
                    ret.isDispersion = s.isDispersions[i];
    866 0
                    ret.isOracleOff = s.isOracleOffs[i];
    867 0
                    return ret;
    868
                }
    869
    870 0
                s.balances[i] = IERC20(s.token).balanceOf(address(this));
    871 0
                s.decimals[i] = IERC20Metadata(s.token).decimals();
    872 0
                if (s.balances[i] > 0) {
    873 0
                    s.totalValueUSD += Math.mulDiv(s.balances[i], s.oraclePriceUSDs[i], 10 ** s.decimals[i]);
    874
                }
    875 0
                if (s.token == token) {
    876 0
                    s.priceUSDToken = s.oraclePriceUSDs[i];
    877 0
                    ret.tokenFound = true;
    878
                }
    879
    880 0
                if (i == s.extendedLength - 1) {
    881
                    // Assign critical info to re-assure no breaker triggered
    882 0
                    ret.assetWithBreakerTriggered = s.token;
    883 0
                    ret.oraclePriceUSD = s.oraclePriceUSDs[i];
    884 0
                    ret.isDepeg = s.isDepegs[i];
    885 0
                    ret.isDispersion = s.isDispersions[i];
    886 0
                    ret.isOracleOff = s.isOracleOffs[i];
    887
                }
    888
            }
    889
    890
            // Initialize the arrays in the return struct
    891 0
            ret.absoluteAllocationPreOperation = new uint256[](s.extendedLength);
    892 0
            ret.absoluteAllocationPostOperation = new uint256[](s.extendedLength);
    893 0
            ret.absoluteTargetAllocation = new uint256[](s.extendedLength);
    894 0
            ret.vaultWeights = new uint256[](s.extendedLength);
    895
    896 0
            uint256 totalSupply_ = totalSupply();
    897
            // PPS = Total Value in USD / Total Supply, normalized to PRECISION
    898 0
            if (totalSupply_ == 0) {
    899 0
                s.superAssetPPS = PRECISION;
    900
            } else {
    901 0
                s.superAssetPPS = Math.mulDiv(s.totalValueUSD, PRECISION, totalSupply_);
    902
            }
    903
    904 0
            ret.amountAssets = Math.mulDiv(amountToken, s.superAssetPPS, s.priceUSDToken);
    905
    906 0
            s.decimalsToken = IERC20Metadata(token).decimals();
    907
    908
            // Adjust for decimals
    909 0
            if (s.decimalsToken < DECIMALS) {
    910 0
                ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (DECIMALS - s.decimalsToken), PRECISION);
    911 0
            } else if (s.decimalsToken > DECIMALS) {
    912 0
                ret.amountAssets = Math.mulDiv(ret.amountAssets, 10 ** (s.decimalsToken - DECIMALS), PRECISION);
    913
            }
    914
    915 0
            s.balanceOfDeltaToken = IERC20(token).balanceOf(address(this));
    916 0
            if (ret.amountAssets > s.balanceOfDeltaToken) {
    917
                // NOTE: Since we do not want this function to revert, we re-set the amount out to the max possible amount
    918
                // out which is the balance of this token
    919
                // NOTE: This should be OK since the user can control the min amount out they desire with the slippage
    920
                // protection
    921 0
                s.deltaToken = s.balanceOfDeltaToken;
    922
            } else {
    923 0
                s.deltaToken = ret.amountAssets;
    924
            }
    925
    926 0
            for (uint256 i; i < s.extendedLength; i++) {
    927 0
                s.token = _supportedAssets.at(i);
    928
                // Convert balance to USD value using price
    929 0
                ret.absoluteAllocationPreOperation[i] =
    930 0
                    Math.mulDiv(s.balances[i], s.oraclePriceUSDs[i], 10 ** s.decimals[i]);
    931 0
                ret.totalAllocationPreOperation += ret.absoluteAllocationPreOperation[i];
    932 0
                ret.absoluteAllocationPostOperation[i] = ret.absoluteAllocationPreOperation[i];
    933 0
                if (s.token == token) {
    934 0
                    s.absDeltaValue = Math.mulDiv(s.deltaToken, s.oraclePriceUSDs[i], 10 ** s.decimals[i]);
    935 0
                    s.deltaValue = -int256(s.absDeltaValue);
    936 0
                    ret.absoluteAllocationPostOperation[i] =
    937 0
                        uint256(int256(ret.absoluteAllocationPreOperation[i]) + s.deltaValue);
    938
                }
    939 0
                ret.totalAllocationPostOperation += ret.absoluteAllocationPostOperation[i];
    940 0
                ret.absoluteTargetAllocation[i] = tokenData[s.token].targetAllocations;
    941 0
                ret.totalTargetAllocation += ret.absoluteTargetAllocation[i];
    942 0
                ret.vaultWeights[i] = tokenData[s.token].weights;
    943
            }
    944
        }
    945
    946
        /// @inheritdoc ISuperAsset
    947 0
        function getPrimaryAsset() external view returns (address) {
    948 0
            return primaryAsset;
    949
        }
    950
    951
        /*//////////////////////////////////////////////////////////////
    952
                                ERC20 OVERRIDES
    953
        //////////////////////////////////////////////////////////////*/
    954
        /// @inheritdoc ERC20
    955
    956 0
        function name() public view override returns (string memory) {
    957 0
            return tokenName;
    958
        }
    959
    960
        /// @inheritdoc ERC20
    961 0
        function symbol() public view override returns (string memory) {
    962 0
            return tokenSymbol;
    963
        }
    964
    965
        /*//////////////////////////////////////////////////////////////
    966
                                INTERNAL FUNCTIONS
    967
        //////////////////////////////////////////////////////////////*/
    968
        /// @dev Settles incentives for a user
    969
        /// @param user The address of the user to settle incentives for
    970
        /// @param amountIncentiveUSD The amount of incentives to settle
    971 0
        function _settleIncentive(address user, int256 amountIncentiveUSD) internal {
    972
            // Pay or take incentives based on the sign of amountIncentive
    973 0
            if (amountIncentiveUSD > 0) {
    974 0
                IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).payIncentive(
    975
                    user, uint256(amountIncentiveUSD)
    976
                );
    977 0
            } else if (amountIncentiveUSD < 0) {
    978 0
                IIncentiveFundContract(factory.getIncentiveFundContract(address(this))).takeIncentive(
    979 0
                    user, uint256(-amountIncentiveUSD)
    980
                );
    981
            }
    982
        }
    983
    984
        // --- Modifiers ---
    985 0
        function _onlyStrategist() internal view {
    986 0
            if (msg.sender != factory.getSuperAssetStrategist(address(this))) revert UNAUTHORIZED();
    987
        }
    988
    989 0
        function _onlyManager() internal view {
    990 0
            if (msg.sender != factory.getSuperAssetManager(address(this))) revert UNAUTHORIZED();
    991
        }
    992
    }
    0% src/SuperAsset/SuperAssetFactory.sol
    Lines covered: 0 / 70 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
    5
    import { ISuperAssetFactory } from "../interfaces/SuperAsset/ISuperAssetFactory.sol";
    6
    import { SuperAsset } from "./SuperAsset.sol";
    7
    import { IncentiveFundContract } from "./IncentiveFundContract.sol";
    8
    import { IncentiveCalculationContract } from "./IncentiveCalculationContract.sol";
    9
    10
    /**
    11
     * @title SuperAssetFactory
    12
     * @author Superform Labs
    13
     * @notice Factory contract that deploys SuperAsset and its dependencies
    14
     */
    15 0
    contract SuperAssetFactory is ISuperAssetFactory {
    16
        using Clones for address;
    17
    18
        /*//////////////////////////////////////////////////////////////
    19
                                    STATE
    20
        //////////////////////////////////////////////////////////////*/
    21 0
        address public immutable superAssetImplementation;
    22 0
        address public immutable incentiveFundImplementation;
    23
        // Single instances
    24 0
        address public immutable superGovernor;
    25
    26 0
        mapping(address superAsset => SuperAssetData data) public data;
    27 0
        mapping(address icc => bool isValid) public incentiveCalculationContractsWhitelist;
    28
    29
        /*//////////////////////////////////////////////////////////////
    30
                                CONSTRUCTOR
    31
        //////////////////////////////////////////////////////////////*/
    32 0
        constructor(address _superGovernor) {
    33 0
            if (_superGovernor == address(0)) revert ZERO_ADDRESS();
    34 0
            superGovernor = _superGovernor;
    35
    36 0
            superAssetImplementation = address(new SuperAsset());
    37 0
            incentiveFundImplementation = address(new IncentiveFundContract());
    38
        }
    39
    40
        /// @inheritdoc ISuperAssetFactory
    41 0
        function addICCToWhitelist(address icc) external {
    42 0
            if (msg.sender != superGovernor) revert UNAUTHORIZED();
    43 0
            incentiveCalculationContractsWhitelist[icc] = true;
    44
        }
    45
    46
        /// @inheritdoc ISuperAssetFactory
    47 0
        function removeICCFromWhitelist(address icc) external {
    48 0
            if (msg.sender != superGovernor) revert UNAUTHORIZED();
    49 0
            incentiveCalculationContractsWhitelist[icc] = false;
    50
        }
    51
    52
        /// @inheritdoc ISuperAssetFactory
    53 0
        function isICCWhitelisted(address icc) external view returns (bool) {
    54 0
            return incentiveCalculationContractsWhitelist[icc];
    55
        }
    56
    57
        /// @inheritdoc ISuperAssetFactory
    58 0
        function setSuperAssetManager(address superAsset, address _superAssetManager) external {
    59 0
            if (_superAssetManager == address(0)) revert ZERO_ADDRESS();
    60 0
            if (
    61 0
                (msg.sender != data[superAsset].superAssetManager) && (msg.sender != superGovernor) // NOTE: This role can
    62
                    // take over
    63 0
            ) revert UNAUTHORIZED();
    64 0
            data[superAsset].superAssetManager = _superAssetManager;
    65
        }
    66
    67
        /// @inheritdoc ISuperAssetFactory
    68 0
        function setSuperAssetStrategist(address superAsset, address _superAssetStrategist) external {
    69 0
            if (_superAssetStrategist == address(0)) revert ZERO_ADDRESS();
    70 0
            if (msg.sender != data[superAsset].superAssetManager) revert UNAUTHORIZED();
    71 0
            data[superAsset].superAssetStrategist = _superAssetStrategist;
    72
        }
    73
    74
        /// @inheritdoc ISuperAssetFactory
    75 0
        function setIncentiveFundManager(address superAsset, address _incentiveFundManager) external {
    76 0
            if (_incentiveFundManager == address(0)) revert ZERO_ADDRESS();
    77 0
            if (msg.sender != data[superAsset].superAssetManager) revert UNAUTHORIZED();
    78 0
            data[superAsset].incentiveFundManager = _incentiveFundManager;
    79
        }
    80
    81
        /// @inheritdoc ISuperAssetFactory
    82 0
        function setIncentiveCalculationContract(address superAsset, address _incentiveCalculationContract) external {
    83 0
            if (_incentiveCalculationContract == address(0)) revert ZERO_ADDRESS();
    84 0
            if (!incentiveCalculationContractsWhitelist[_incentiveCalculationContract]) revert ICC_NOT_WHITELISTED();
    85 0
            if (msg.sender != data[superAsset].superAssetManager) revert UNAUTHORIZED();
    86 0
            data[superAsset].incentiveCalculationContract = _incentiveCalculationContract;
    87
        }
    88
    89
        /// @inheritdoc ISuperAssetFactory
    90 0
        function getSuperAssetManager(address superAsset) external view returns (address) {
    91 0
            return data[superAsset].superAssetManager;
    92
        }
    93
    94
        /// @inheritdoc ISuperAssetFactory
    95 0
        function getSuperAssetStrategist(address superAsset) external view returns (address) {
    96 0
            return data[superAsset].superAssetStrategist;
    97
        }
    98
    99
        /// @inheritdoc ISuperAssetFactory
    100 0
        function getIncentiveFundManager(address superAsset) external view returns (address) {
    101 0
            return data[superAsset].incentiveFundManager;
    102
        }
    103
    104
        /// @inheritdoc ISuperAssetFactory
    105 0
        function getIncentiveCalculationContract(address superAsset) external view returns (address) {
    106 0
            return data[superAsset].incentiveCalculationContract;
    107
        }
    108
    109
        /// @inheritdoc ISuperAssetFactory
    110 0
        function getIncentiveFundContract(address superAsset) external view returns (address) {
    111 0
            return data[superAsset].incentiveFundContract;
    112
        }
    113
    114
        /*//////////////////////////////////////////////////////////////
    115
                                EXTERNAL FUNCTIONS
    116
        //////////////////////////////////////////////////////////////*/
    117
        /// @inheritdoc ISuperAssetFactory
    118 0
        function createSuperAsset(AssetCreationParams calldata params)
    119
            external
    120 0
            returns (address superAsset, address incentiveFundContract)
    121
        {
    122
            // TODO: Decide whether to make this method permissionless or permissioned
    123
    124 0
            if (params.incentiveCalculationContract == address(0)) revert ZERO_ADDRESS();
    125 0
            if (!incentiveCalculationContractsWhitelist[params.incentiveCalculationContract]) revert ICC_NOT_WHITELISTED();
    126
    127
            // Deploy IncentiveFund (this one needs to be unique per SuperAsset)
    128 0
            incentiveFundContract = incentiveFundImplementation.clone();
    129
    130
            // Deploy SuperAsset with its dependencies
    131 0
            superAsset = superAssetImplementation.clone();
    132 0
            SuperAsset(superAsset).initialize(
    133 0
                params.name,
    134 0
                params.symbol,
    135 0
                params.asset,
    136 0
                superGovernor,
    137 0
                params.swapFeeInPercentage,
    138 0
                params.swapFeeOutPercentage
    139
            );
    140
    141
            // Initialize IncentiveFund
    142 0
            IncentiveFundContract(incentiveFundContract).initialize(
    143 0
                superGovernor, superAsset, params.tokenInIncentive, params.tokenOutIncentive
    144
            );
    145
    146 0
            data[superAsset] = SuperAssetData({
    147 0
                superAssetManager: params.superAssetManager,
    148 0
                superAssetStrategist: params.superAssetStrategist,
    149 0
                incentiveFundManager: params.incentiveFundManager,
    150 0
                incentiveCalculationContract: params.incentiveCalculationContract,
    151
                incentiveFundContract: incentiveFundContract
    152
            });
    153
    154 0
            emit SuperAssetCreated(
    155 0
                superAsset, incentiveFundContract, params.incentiveCalculationContract, params.name, params.symbol
    156
            );
    157
        }
    158
    }
    0% src/SuperBank.sol
    Lines covered: 0 / 27 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // External
    5
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    6
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    7
    import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
    8
    9
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    10
    11
    // Superform
    12
    import { ISuperBank } from "./interfaces/ISuperBank.sol";
    13
    import { ISuperGovernor, FeeType } from "./interfaces/ISuperGovernor.sol";
    14
    import { Bank } from "./Bank.sol";
    15
    16
    /// @title SuperBank
    17
    /// @notice Compounds protocol revenue into UP and distributes it to sUP and treasury.
    18 0
    contract SuperBank is ISuperBank, Bank {
    19
        using SafeERC20 for IERC20;
    20
        using Math for uint256;
    21
    22 0
        uint256 private constant BPS_MAX = 10_000;
    23 0
        ISuperGovernor public immutable SUPER_GOVERNOR;
    24
    25 0
        constructor(address superGovernor_) {
    26 0
            if (superGovernor_ == address(0)) revert INVALID_ADDRESS();
    27 0
            SUPER_GOVERNOR = ISuperGovernor(superGovernor_);
    28
        }
    29
    30
        modifier onlyBankManager() {
    31 0
            if (!IAccessControl(address(SUPER_GOVERNOR)).hasRole(SUPER_GOVERNOR.BANK_MANAGER_ROLE(), msg.sender)) {
    32 0
                revert INVALID_BANK_MANAGER();
    33
            }
    34
            _;
    35
        }
    36
    37
        /*//////////////////////////////////////////////////////////////
    38
                            EXTERNAL FUNCTIONS
    39
        //////////////////////////////////////////////////////////////*/
    40
    41
        // Receive function to accept direct ETH transfers if needed for hooks/executions
    42
        receive() external payable { }
    43
    44
        /// @inheritdoc ISuperBank
    45 0
        function distribute(uint256 upAmount) external onlyBankManager {
    46 0
            if (upAmount == 0) revert ZERO_LENGTH_ARRAY();
    47
    48
            // Get UP token address from SuperGovernor
    49 0
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    50 0
            address supToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUP());
    51 0
            address treasury = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.TREASURY());
    52
    53
            // Get revenue share percentage from SuperGovernor
    54 0
            uint256 revenueShare = SUPER_GOVERNOR.getFee(FeeType.REVENUE_SHARE);
    55
    56
            // Calculate amounts for sUP and Treasury
    57 0
            uint256 supAmount = upAmount.mulDiv(revenueShare, BPS_MAX);
    58
    59 0
            uint256 treasuryAmount = upAmount - supAmount;
    60
    61
            // Get the UP token instance
    62 0
            IERC20 up = IERC20(upToken);
    63
    64
            // Ensure we have the tokens
    65 0
            if (up.balanceOf(address(this)) < upAmount) revert INVALID_UP_AMOUNT_TO_DISTRIBUTE();
    66
    67
            // Transfer tokens to sUP and Treasury
    68 0
            if (supAmount > 0) {
    69 0
                up.safeTransfer(supToken, supAmount);
    70
            }
    71
    72 0
            if (treasuryAmount > 0) {
    73 0
                up.safeTransfer(treasury, treasuryAmount);
    74
            }
    75
    76 0
            emit RevenueDistributed(upToken, supToken, treasury, supAmount, treasuryAmount);
    77
        }
    78
    79
        /// @inheritdoc ISuperBank
    80 0
        function executeHooks(ISuperBank.HookExecutionData calldata executionData) external payable onlyBankManager {
    81 0
            _executeHooks(executionData);
    82
        }
    83
    84
        /*//////////////////////////////////////////////////////////////
    85
                            INTERNAL FUNCTIONS
    86
        //////////////////////////////////////////////////////////////*/
    87 0
        function _getMerkleRootForHook(address hookAddress) internal view override returns (bytes32) {
    88 0
            return SUPER_GOVERNOR.getSuperBankHookMerkleRoot(hookAddress);
    89
        }
    90
    }
    24% src/SuperGovernor.sol
    Lines covered: 112 / 457 (24%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
    6
    import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    7
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    8
    9
    // Superform
    10
    import { ISuperGovernor, FeeType } from "./interfaces/ISuperGovernor.sol";
    11
    import { ISuperVaultAggregator } from "./interfaces/SuperVault/ISuperVaultAggregator.sol";
    12
    import { ISuperAssetFactory } from "./interfaces/SuperAsset/ISuperAssetFactory.sol";
    13
    import { ISuperOracle } from "./interfaces/oracles/ISuperOracle.sol";
    14
    import { ISuperOracleL2 } from "./interfaces/oracles/ISuperOracleL2.sol";
    15
    16
    /// @title SuperGovernor
    17
    /// @author Superform Labs
    18
    /// @notice Central registry for all deployed contracts in the Superform periphery
    19 1254×
    contract SuperGovernor is ISuperGovernor, AccessControl {
    20
        using EnumerableSet for EnumerableSet.AddressSet;
    21
    22
        /*//////////////////////////////////////////////////////////////
    23
                                     STORAGE
    24
        //////////////////////////////////////////////////////////////*/
    25
        // Address registry
    26
        mapping(bytes32 id => address address_) private _addressRegistry;
    27
    28
        // PPS Oracle management
    29
        // Active PPS Oracle quorum requirement
    30
        uint256 private _activePPSOracleQuorum;
    31
        // Current active PPS Oracle
    32
        address private _activePPSOracle;
    33
        // Proposed new active PPS Oracle
    34
        address private _proposedActivePPSOracle;
    35
        // Effective time for proposed active PPS Oracle change
    36
        uint256 private _activePPSOracleEffectiveTime;
    37
    38
        // Hook registry
    39
        EnumerableSet.AddressSet private _registeredHooks;
    40
        EnumerableSet.AddressSet private _registeredFulfillRequestsHooks;
    41
    42
        // SuperBank Hook Target validation
    43
        mapping(address hook => ISuperGovernor.HookMerkleRootData merkleData) private superBankHooksMerkleRoots;
    44
    45
        // VaultBank registry
    46
        EnumerableSet.AddressSet private _vaultBanks;
    47
        mapping(uint64 chainId => address vaultBank) private _vaultBanksByChainId;
    48
    49
        // VaultBank Hook Target validation
    50
        mapping(address hook => ISuperGovernor.HookMerkleRootData merkleData) private vaultBankHooksMerkleRoots;
    51
    52
        // Global freeze for manager takeovers
    53
        bool private _managerTakeoversFrozen;
    54
    55
        // Validator registry
    56
        EnumerableSet.AddressSet private _validators;
    57
    58
        // Relayer registry
    59
        EnumerableSet.AddressSet private _relayers;
    60
    61
        // Protected keepers registry (cannot be added as authorized callers by managers)
    62
        EnumerableSet.AddressSet private _protectedKeepers;
    63
    64
        // Executor registry
    65
        EnumerableSet.AddressSet private _executors;
    66
    67
        // Polymer prover
    68
        address private _prover;
    69
    70
        // Whitelisted incentive tokens
    71
        mapping(address token => bool isWhitelisted) private _isWhitelistedIncentiveToken;
    72
        EnumerableSet.AddressSet private _proposedWhitelistedIncentiveTokens;
    73
        EnumerableSet.AddressSet private _proposedRemoveWhitelistedIncentiveTokens;
    74
        uint256 private _proposedAddWhitelistedIncentiveTokensEffectiveTime;
    75
        uint256 private _proposedRemoveWhitelistedIncentiveTokensEffectiveTime;
    76
    77
        // Fee management
    78
        // Current fee values
    79
        mapping(FeeType type_ => uint256 value) private _feeValues;
    80
        // Proposed fee values
    81
        mapping(FeeType type_ => uint256 proposedValue) private _proposedFeeValues;
    82
        // Effective times for proposed fee updates
    83
        mapping(FeeType type_ => uint256 effectiveTime) private _feeEffectiveTimes;
    84
    85
        mapping(address _oracle => GasInfo info) private _oracleGasInfo;
    86
    87
        // Upkeep control
    88
        bool private _upkeepPaymentsEnabled;
    89
        bool private _proposedUpkeepPaymentsEnabled;
    90
        uint256 private _upkeepPaymentsChangeEffectiveTime;
    91
    92
        // Superform managers (exempt from upkeep costs)
    93
        EnumerableSet.AddressSet private _superformManagers;
    94
    95
        // Min staleness configuration to prevent maxStaleness from being set too low
    96
        uint256 private _minStaleness;
    97
        uint256 private _proposedMinStaleness;
    98
        uint256 private _minStalenesEffectiveTime;
    99
    100
        // Oracle constants
    101 0
        address private constant NATIVE_TOKEN = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
    102 0
        address private constant USD_TOKEN = address(840);
    103
        address private constant GAS_QUOTE =
    104
            address(uint160(uint256(keccak256("GAS_QUOTE"))));
    105
        address private constant GWEI_QUOTE =
    106
            address(uint160(uint256(keccak256("GWEI_QUOTE"))));
    107 0
        bytes32 private constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
    108
    109
        // Timelock configuration
    110
        uint256 private constant TIMELOCK = 7 days;
    111
        uint256 private constant BPS_MAX = 10_000; // 100% in basis points
    112
    113
        // Role definitions
    114
        bytes32 private constant _SUPER_GOVERNOR_ROLE = keccak256("SUPER_GOVERNOR_ROLE");
    115
        bytes32 private constant _GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
    116 0
        bytes32 private constant _BANK_MANAGER_ROLE = keccak256("BANK_MANAGER_ROLE");
    117 0
        bytes32 private constant _GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE");
    118
        bytes32 private constant _SUPER_ASSET_FACTORY = keccak256("SUPER_ASSET_FACTORY");
    119 0
        bytes32 private constant _GAS_MANAGER_ROLE = keccak256("GAS_MANAGER_ROLE");
    120
    121
        // Common contract keys
    122 10×
        bytes32 public constant UP = keccak256("UP");
    123 0
        bytes32 public constant SUP = keccak256("SUP");
    124 0
        bytes32 public constant TREASURY = keccak256("TREASURY");
    125 0
        bytes32 public constant VAULT_BANK = keccak256("VAULT_BANK");
    126
        bytes32 public constant SUPER_BANK = keccak256("SUPER_BANK");
    127 0
        bytes32 public constant SUPER_ORACLE = keccak256("SUPER_ORACLE");
    128 0
        bytes32 public constant BANK_MANAGER = keccak256("BANK_MANAGER");
    129 0
        bytes32 public constant ECDSAPPSORACLE = keccak256("ECDSAPPSORACLE");
    130 16×
        bytes32 public constant SUPER_VAULT_AGGREGATOR = keccak256("SUPER_VAULT_AGGREGATOR");
    131
    132
        /*//////////////////////////////////////////////////////////////
    133
                                  CONSTRUCTOR
    134
        //////////////////////////////////////////////////////////////*/
    135
        /// @notice Initializes the SuperGovernor contract
    136
        /// @param superGovernor Address of the default admin (will have SUPER_GOVERNOR_ROLE)
    137
        /// @param governor Address that will have the GOVERNOR_ROLE for daily operations
    138
        /// @param bankManager Address that will have the BANK_MANAGER_ROLE for daily operations
    139
        /// @param treasury_ Address of the treasury
    140
        /// @param prover_ Address of the prover
    141 0
        constructor(address superGovernor, address governor, address bankManager, address gasManager, address treasury_, address prover_) {
    142 0
            if (
    143 0
                superGovernor == address(0) || treasury_ == address(0) || governor == address(0)
    144 0
                    || bankManager == address(0) || prover_ == address(0) || gasManager == address(0)
    145 0
            ) revert INVALID_ADDRESS();
    146
    147
            // Set up roles
    148 0
            _grantRole(DEFAULT_ADMIN_ROLE, superGovernor);
    149 0
            _grantRole(_SUPER_GOVERNOR_ROLE, superGovernor);
    150 0
            _grantRole(_GOVERNOR_ROLE, governor);
    151 0
            _grantRole(_BANK_MANAGER_ROLE, bankManager);
    152 0
            _grantRole(_GAS_MANAGER_ROLE, gasManager);
    153
            // Setup GUARDIAN_ROLE without assigning any address
    154 0
            _setRoleAdmin(_GUARDIAN_ROLE, DEFAULT_ADMIN_ROLE);
    155
    156
            // Set role admins
    157 0
            _setRoleAdmin(_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE);
    158 0
            _setRoleAdmin(_SUPER_GOVERNOR_ROLE, DEFAULT_ADMIN_ROLE);
    159 0
            _setRoleAdmin(_BANK_MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
    160 0
            _setRoleAdmin(_GAS_MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
    161
    162
            // Initialize with default fees
    163 0
            _feeValues[FeeType.REVENUE_SHARE] = 2000; // 20% revenue share
    164 0
            _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE] = 2000; // 20% performance fee
    165 0
            _feeValues[FeeType.SUPER_ASSET_SWAP_FEE] = 4000; // 40% swap fee
    166 0
            emit FeeUpdated(FeeType.REVENUE_SHARE, _feeValues[FeeType.REVENUE_SHARE]);
    167 0
            emit FeeUpdated(FeeType.SUPER_VAULT_PERFORMANCE_FEE, _feeValues[FeeType.SUPER_VAULT_PERFORMANCE_FEE]);
    168 0
            emit FeeUpdated(FeeType.SUPER_ASSET_SWAP_FEE, _feeValues[FeeType.SUPER_ASSET_SWAP_FEE]);
    169
    170
            // Set treasury in address registry
    171 0
            _addressRegistry[TREASURY] = treasury_;
    172 0
            emit AddressSet(TREASURY, treasury_);
    173
    174
            // Initialize minimum staleness (5 minutes to prevent extremely low staleness values)
    175 0
            _minStaleness = 300; // 5 minutes in seconds
    176
    177
            // Initialize prover
    178 0
            _prover = prover_;
    179 0
            emit ProverSet(prover_);
    180
        }
    181
    182
        /*//////////////////////////////////////////////////////////////
    183
                           CONTRACT REGISTRY FUNCTIONS
    184
        //////////////////////////////////////////////////////////////*/
    185
        /// @inheritdoc ISuperGovernor
    186 0
        function setAddress(bytes32 key, address value) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    187 0
            if (value == address(0)) revert INVALID_ADDRESS();
    188
    189 0
            _addressRegistry[key] = value;
    190 0
            emit AddressSet(key, value);
    191
        }
    192
    193
        /*//////////////////////////////////////////////////////////////
    194
                        PERIPHERY CONFIGURATIONS
    195
        //////////////////////////////////////////////////////////////*/
    196
        /// @inheritdoc ISuperGovernor
    197 0
        function setProver(address prover) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    198 0
            if (prover == address(0)) revert INVALID_ADDRESS();
    199
    200 0
            _prover = prover;
    201 0
            emit ProverSet(prover);
    202
        }
    203
    204
        /// @inheritdoc ISuperGovernor
    205 11×
        function changePrimaryManager(address strategy, address newManager) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    206
            // Check if takeovers are globally frozen
    207
            if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN();
    208
    209
            address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR];
    210
            if (aggregator == address(0)) revert CONTRACT_NOT_FOUND();
    211
    212
            // Call the interface method to change the manager
    213
            // This function can only be called by the SuperGovernor and bypasses the timelock
    214 13×
            ISuperVaultAggregator(aggregator).changePrimaryManager(strategy, newManager);
    215
        }
    216
    217
        /// @inheritdoc ISuperGovernor
    218 0
        function freezeManagerTakeover() external onlyRole(_SUPER_GOVERNOR_ROLE) {
    219 0
            if (_managerTakeoversFrozen) revert MANAGER_TAKEOVERS_FROZEN();
    220
    221
            // Set frozen status to true (permanent, cannot be undone)
    222 0
            _managerTakeoversFrozen = true;
    223
    224
            // Emit event for the frozen status
    225 0
            emit ManagerTakeoversFrozen();
    226
        }
    227
    228
        /// @inheritdoc ISuperGovernor
    229 0
        function changeHooksRootUpdateTimelock(uint256 newTimelock) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    230 0
            address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR];
    231 0
            if (aggregator == address(0)) revert CONTRACT_NOT_FOUND();
    232
    233
            // Call the SuperVaultAggregator to change the hooks root update timelock
    234 0
            ISuperVaultAggregator(aggregator).setHooksRootUpdateTimelock(newTimelock);
    235
        }
    236
    237
        /// @inheritdoc ISuperGovernor
    238 11×
        function proposeGlobalHooksRoot(bytes32 newRoot) external onlyRole(_GOVERNOR_ROLE) {
    239
            address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR];
    240
            if (aggregator == address(0)) revert CONTRACT_NOT_FOUND();
    241
    242 13×
            ISuperVaultAggregator(aggregator).proposeGlobalHooksRoot(newRoot);
    243
        }
    244
    245
        /// @inheritdoc ISuperGovernor
    246 0
        function setGlobalHooksRootVetoStatus(bool vetoed) external onlyRole(_GUARDIAN_ROLE) {
    247 0
            address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR];
    248 0
            if (aggregator == address(0)) revert CONTRACT_NOT_FOUND();
    249
    250 0
            ISuperVaultAggregator(aggregator).setGlobalHooksRootVetoStatus(vetoed);
    251
        }
    252
    253
        /// @inheritdoc ISuperGovernor
    254 0
        function setStrategyHooksRootVetoStatus(address strategy, bool vetoed) external onlyRole(_GUARDIAN_ROLE) {
    255 0
            if (strategy == address(0)) revert INVALID_ADDRESS();
    256
    257 0
            address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR];
    258 0
            if (aggregator == address(0)) revert CONTRACT_NOT_FOUND();
    259
    260 0
            ISuperVaultAggregator(aggregator).setStrategyHooksRootVetoStatus(strategy, vetoed);
    261
        }
    262
    263
        /// @inheritdoc ISuperGovernor
    264 0
        function setSuperAssetManager(address superAsset, address superAssetManager) external onlyRole(_GOVERNOR_ROLE) {
    265 0
            if (superAsset == address(0) || superAssetManager == address(0)) revert INVALID_ADDRESS();
    266 0
            address value = _addressRegistry[_SUPER_ASSET_FACTORY];
    267 0
            if (value == address(0)) revert CONTRACT_NOT_FOUND();
    268 0
            ISuperAssetFactory factory = ISuperAssetFactory(value);
    269 0
            factory.setSuperAssetManager(superAsset, superAssetManager);
    270
        }
    271
    272
        /// @inheritdoc ISuperGovernor
    273 0
        function addICCToWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    274 0
            if (icc == address(0)) revert INVALID_ADDRESS();
    275 0
            address value = _addressRegistry[_SUPER_ASSET_FACTORY];
    276 0
            if (value == address(0)) revert CONTRACT_NOT_FOUND();
    277 0
            ISuperAssetFactory factory = ISuperAssetFactory(value);
    278 0
            factory.addICCToWhitelist(icc);
    279
        }
    280
    281
        /// @inheritdoc ISuperGovernor
    282 0
        function removeICCFromWhitelist(address icc) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    283 0
            if (icc == address(0)) revert INVALID_ADDRESS();
    284 0
            address value = _addressRegistry[_SUPER_ASSET_FACTORY];
    285 0
            if (value == address(0)) revert CONTRACT_NOT_FOUND();
    286 0
            ISuperAssetFactory factory = ISuperAssetFactory(value);
    287 0
            factory.removeICCFromWhitelist(icc);
    288
        }
    289
    290
        /// @inheritdoc ISuperGovernor
    291 0
        function setOracleMaxStaleness(uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) {
    292 0
            if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW();
    293 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    294 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    295
    296 0
            ISuperOracle(oracle).setMaxStaleness(newMaxStaleness);
    297
        }
    298
    299
        /// @inheritdoc ISuperGovernor
    300
        function setOracleFeedMaxStaleness(address feed, uint256 newMaxStaleness) external onlyRole(_GOVERNOR_ROLE) {
    301 0
            if (feed == address(0)) revert INVALID_ADDRESS();
    302 0
            if (newMaxStaleness < _minStaleness) revert MAX_STALENESS_TOO_LOW();
    303 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    304 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    305
    306 31×
            ISuperOracle(oracle).setFeedMaxStaleness(feed, newMaxStaleness);
    307
        }
    308
    309
        /// @inheritdoc ISuperGovernor
    310 0
        function setOracleFeedMaxStalenessBatch(
    311
            address[] calldata feeds_,
    312
            uint256[] calldata newMaxStalenessList_
    313
        )
    314
            external
    315
            onlyRole(_GOVERNOR_ROLE)
    316
        {
    317
            // Validate all staleness values before proceeding
    318 0
            for (uint256 i; i < newMaxStalenessList_.length; i++) {
    319 0
                if (newMaxStalenessList_[i] < _minStaleness) revert MAX_STALENESS_TOO_LOW();
    320
            }
    321
    322 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    323 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    324
    325 0
            ISuperOracle(oracle).setFeedMaxStalenessBatch(feeds_, newMaxStalenessList_);
    326
        }
    327
    328
        /// @inheritdoc ISuperGovernor
    329 0
        function queueOracleUpdate(
    330
            address[] calldata bases_,
    331
            address[] calldata quotes_,
    332
            bytes32[] calldata providers_,
    333
            address[] calldata feeds_
    334
        )
    335
            external
    336
            onlyRole(_GOVERNOR_ROLE)
    337 0
        {
    338 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    339 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    340
    341 0
            ISuperOracle(oracle).queueOracleUpdate(bases_, quotes_, providers_, feeds_);
    342
        }
    343
    344
        /// @inheritdoc ISuperGovernor
    345 0
        function queueOracleProviderRemoval(bytes32[] calldata providers) external onlyRole(_GOVERNOR_ROLE) {
    346 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    347 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    348
    349 0
            ISuperOracle(oracle).queueProviderRemoval(providers);
    350
        }
    351
    352
        /// @inheritdoc ISuperGovernor
    353 0
        function batchSetOracleUptimeFeed(
    354
            address[] calldata dataOracles_,
    355
            address[] calldata uptimeOracles_,
    356
            uint256[] calldata gracePeriods_
    357
        )
    358
            external
    359
            onlyRole(_GOVERNOR_ROLE)
    360 0
        {
    361 0
            address oracleL2 = _addressRegistry[SUPER_ORACLE];
    362 0
            if (oracleL2 == address(0)) revert CONTRACT_NOT_FOUND();
    363
    364 0
            ISuperOracleL2(oracleL2).batchSetUptimeFeed(dataOracles_, uptimeOracles_, gracePeriods_);
    365
        }
    366
    367
        /// @inheritdoc ISuperGovernor
    368 0
        function setEmergencyPrice(address token, uint256 price) external onlyRole(_GOVERNOR_ROLE) {
    369 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    370 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    371
    372 0
            ISuperOracle(oracle).setEmergencyPrice(token, price);
    373
        }
    374
    375
        /// @inheritdoc ISuperGovernor
    376
        function batchSetEmergencyPrices(
    377
            address[] calldata tokens_,
    378
            uint256[] calldata prices_
    379
        )
    380
            external
    381
            onlyRole(_GOVERNOR_ROLE)
    382 0
        {
    383 0
            address oracle = _addressRegistry[SUPER_ORACLE];
    384 0
            if (oracle == address(0)) revert CONTRACT_NOT_FOUND();
    385
    386 0
            ISuperOracle(oracle).batchSetEmergencyPrice(tokens_, prices_);
    387
        }
    388
    389
        /*//////////////////////////////////////////////////////////////
    390
                                HOOK MANAGEMENT
    391
        //////////////////////////////////////////////////////////////*/
    392
        /// @inheritdoc ISuperGovernor
    393 0
        function registerHook(address hook, bool isFulfillRequestsHook) external onlyRole(_GOVERNOR_ROLE) {
    394 0
            if (hook == address(0)) revert INVALID_ADDRESS();
    395
    396 0
            if (isFulfillRequestsHook && _registeredFulfillRequestsHooks.add(hook)) {
    397 0
                emit FulfillRequestsHookRegistered(hook);
    398
            }
    399 0
            if (_registeredHooks.add(hook)) {
    400 0
                emit HookApproved(hook);
    401
            }
    402
        }
    403
    404
        /// @inheritdoc ISuperGovernor
    405
        function unregisterHook(address hook) external onlyRole(_GOVERNOR_ROLE) {
    406 0
            if (_registeredFulfillRequestsHooks.remove(hook)) {
    407 0
                emit FulfillRequestsHookUnregistered(hook);
    408
            }
    409
            if (_registeredHooks.remove(hook)) {
    410
                // Clear merkle root data for the unregistered hook to prevent stale data
    411 0
                delete superBankHooksMerkleRoots[hook];
    412 0
                delete vaultBankHooksMerkleRoots[hook];
    413
    414 0
                emit HookRemoved(hook);
    415
            }
    416
        }
    417
    418
        /*//////////////////////////////////////////////////////////////
    419
                            EXECUTORS MANAGEMENT
    420
        //////////////////////////////////////////////////////////////*/
    421
        /// @inheritdoc ISuperGovernor
    422 0
        function addExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) {
    423 0
            if (executor == address(0)) revert INVALID_ADDRESS();
    424 0
            if (!_executors.add(executor)) revert EXECUTOR_ALREADY_REGISTERED();
    425
    426 0
            emit ExecutorAdded(executor);
    427
        }
    428
    429
        /// @inheritdoc ISuperGovernor
    430 0
        function removeExecutor(address executor) external onlyRole(_GOVERNOR_ROLE) {
    431 0
            if (!_executors.remove(executor)) revert EXECUTOR_NOT_REGISTERED();
    432
    433 0
            emit ExecutorRemoved(executor);
    434
        }
    435
    436
        /*//////////////////////////////////////////////////////////////
    437
                              RELAYER MANAGEMENT
    438
        //////////////////////////////////////////////////////////////*/
    439
        /// @inheritdoc ISuperGovernor
    440 0
        function addRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) {
    441 0
            if (relayer == address(0)) revert INVALID_ADDRESS();
    442 0
            if (!_relayers.add(relayer)) revert RELAYER_ALREADY_REGISTERED();
    443
    444 0
            emit RelayerAdded(relayer);
    445
        }
    446
    447
        /// @inheritdoc ISuperGovernor
    448 0
        function removeRelayer(address relayer) external onlyRole(_GOVERNOR_ROLE) {
    449 0
            if (!_relayers.remove(relayer)) revert RELAYER_NOT_REGISTERED();
    450
    451 0
            emit RelayerRemoved(relayer);
    452
        }
    453
    454
        /*//////////////////////////////////////////////////////////////
    455
                            VALIDATOR MANAGEMENT
    456
        //////////////////////////////////////////////////////////////*/
    457
        /// @inheritdoc ISuperGovernor
    458 0
        function addValidator(address validator) external onlyRole(_GOVERNOR_ROLE) {
    459 0
            if (validator == address(0)) revert INVALID_ADDRESS();
    460 0
            if (!_validators.add(validator)) revert VALIDATOR_ALREADY_REGISTERED();
    461
    462 0
            emit ValidatorAdded(validator);
    463
        }
    464
    465
        /// @inheritdoc ISuperGovernor
    466 0
        function removeValidator(address validator) external onlyRole(_GOVERNOR_ROLE) {
    467 0
            if (!_validators.remove(validator)) revert VALIDATOR_NOT_REGISTERED();
    468
    469 0
            emit ValidatorRemoved(validator);
    470
        }
    471
    472
        /*//////////////////////////////////////////////////////////////
    473
                             PPS ORACLE MANAGEMENT
    474
        //////////////////////////////////////////////////////////////*/
    475
        /// @inheritdoc ISuperGovernor
    476 0
        function setActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    477 0
            if (oracle == address(0)) revert INVALID_ADDRESS();
    478
    479
            // If this is the first oracle or replacing a zero oracle, set it immediately
    480 0
            if (_activePPSOracle == address(0)) {
    481 0
                _activePPSOracle = oracle;
    482 0
                emit ActivePPSOracleSet(oracle);
    483
            } else {
    484
                // Otherwise require the timelock process
    485 0
                revert MUST_USE_TIMELOCK_FOR_CHANGE();
    486
            }
    487
        }
    488
    489
        /// @inheritdoc ISuperGovernor
    490
        function proposeActivePPSOracle(address oracle) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    491 0
            if (oracle == address(0)) revert INVALID_ADDRESS();
    492
    493 0
            _proposedActivePPSOracle = oracle;
    494 0
            _activePPSOracleEffectiveTime = block.timestamp + TIMELOCK;
    495
    496
            emit ActivePPSOracleProposed(oracle, _activePPSOracleEffectiveTime);
    497
        }
    498
    499
        /// @inheritdoc ISuperGovernor
    500 0
        function executeActivePPSOracleChange() external {
    501 0
            if (_proposedActivePPSOracle == address(0)) revert NO_PROPOSED_PPS_ORACLE();
    502
    503 0
            if (block.timestamp < _activePPSOracleEffectiveTime) {
    504 0
                revert TIMELOCK_NOT_EXPIRED();
    505
            }
    506
    507 0
            address oldOracle = _activePPSOracle;
    508 0
            _activePPSOracle = _proposedActivePPSOracle;
    509
    510
            // Reset proposal data
    511 0
            _proposedActivePPSOracle = address(0);
    512 0
            _activePPSOracleEffectiveTime = 0;
    513
    514 0
            emit ActivePPSOracleChanged(oldOracle, _activePPSOracle);
    515
        }
    516
    517
        /// @inheritdoc ISuperGovernor
    518 0
        function setPPSOracleQuorum(uint256 quorum) external onlyRole(_GOVERNOR_ROLE) {
    519 0
            if (quorum == 0) revert INVALID_QUORUM();
    520
    521 0
            _activePPSOracleQuorum = quorum;
    522 0
            emit PPSOracleQuorumUpdated(quorum);
    523
        }
    524
    525
        /*//////////////////////////////////////////////////////////////
    526
                          REVENUE SHARE MANAGEMENT
    527
        //////////////////////////////////////////////////////////////*/
    528
        /// @inheritdoc ISuperGovernor
    529 15×
        function proposeFee(FeeType feeType, uint256 value) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    530 19×
            if (value > BPS_MAX) revert INVALID_FEE_VALUE();
    531
    532 31×
            _proposedFeeValues[feeType] = value;
    533 35×
            _feeEffectiveTimes[feeType] = block.timestamp + TIMELOCK;
    534
    535 53×
            emit FeeProposed(feeType, value, _feeEffectiveTimes[feeType]);
    536
        }
    537
    538
        /// @inheritdoc ISuperGovernor
    539 11×
        function executeFeeUpdate(FeeType feeType) external {
    540 32×
            uint256 effectiveTime = _feeEffectiveTimes[feeType];
    541 26×
            if (effectiveTime == 0) revert NO_PROPOSED_FEE(feeType);
    542
            if (block.timestamp < effectiveTime) {
    543 13×
                revert TIMELOCK_NOT_EXPIRED();
    544
            }
    545
    546
            // Update the fee value
    547 61×
            _feeValues[feeType] = _proposedFeeValues[feeType];
    548
    549
            // Reset proposal data
    550 31×
            delete _proposedFeeValues[feeType];
    551 31×
            delete _feeEffectiveTimes[feeType];
    552
    553 42×
            emit FeeUpdated(feeType, _feeValues[feeType]);
    554
        }
    555
    556
        /// @inheritdoc ISuperGovernor
    557 15×
        function executeUpkeepClaim(uint256 amount) external onlyRole(_GOVERNOR_ROLE) {
    558
            address aggregator = _addressRegistry[SUPER_VAULT_AGGREGATOR];
    559
            if (aggregator == address(0)) revert CONTRACT_NOT_FOUND();
    560
    561 50×
            ISuperVaultAggregator(aggregator).claimUpkeep(amount);
    562
        }
    563
    564
        /*//////////////////////////////////////////////////////////////
    565
                            UPKEEP COST MANAGEMENT
    566
        //////////////////////////////////////////////////////////////*/
    567
        /// @inheritdoc ISuperGovernor
    568 0
        function setGasInfo(address oracle, uint256 baseGasBatch, uint256 gasIncreasePerEntryBatch) external onlyRole(_GAS_MANAGER_ROLE) {
    569 0
            if (oracle == address(0)) revert INVALID_ADDRESS();
    570 0
            if (baseGasBatch == 0 || gasIncreasePerEntryBatch == 0) revert INVALID_GAS_INFO();
    571
    572 0
            _oracleGasInfo[oracle] = GasInfo({baseGasBatch: baseGasBatch, gasIncreasePerEntryBatch: gasIncreasePerEntryBatch});
    573 0
            emit GasInfoSet(oracle, baseGasBatch, gasIncreasePerEntryBatch);
    574
        }
    575
    576
        /// @notice Proposes a change to the upkeep payments enabled status
    577
        /// @param enabled The proposed new status for upkeep payments
    578 11×
        function proposeUpkeepPaymentsChange(bool enabled) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    579 12×
            _proposedUpkeepPaymentsEnabled = enabled;
    580
            _upkeepPaymentsChangeEffectiveTime = block.timestamp + TIMELOCK;
    581
    582
            emit UpkeepPaymentsChangeProposed(enabled, _upkeepPaymentsChangeEffectiveTime);
    583
        }
    584
    585
        /// @notice Executes a previously proposed change to upkeep payments status after timelock expires
    586
        function executeUpkeepPaymentsChange() external {
    587 20×
            if (_upkeepPaymentsChangeEffectiveTime == 0) revert NO_PENDING_CHANGE();
    588 21×
            if (block.timestamp < _upkeepPaymentsChangeEffectiveTime) revert TIMELOCK_NOT_EXPIRED();
    589
    590 14×
            _upkeepPaymentsEnabled = _proposedUpkeepPaymentsEnabled;
    591
            _upkeepPaymentsChangeEffectiveTime = 0;
    592 11×
            _proposedUpkeepPaymentsEnabled = false;
    593
    594 13×
            emit UpkeepPaymentsChanged(_upkeepPaymentsEnabled);
    595
        }
    596
    597
        /*//////////////////////////////////////////////////////////////
    598
                            MIN STALENESS MANAGEMENT
    599
        //////////////////////////////////////////////////////////////*/
    600
        /// @inheritdoc ISuperGovernor
    601 11×
        function proposeMinStaleness(uint256 newMinStaleness) external onlyRole(_SUPER_GOVERNOR_ROLE) {
    602
            _proposedMinStaleness = newMinStaleness;
    603
            _minStalenesEffectiveTime = block.timestamp + TIMELOCK;
    604
    605
            emit MinStalenesProposed(newMinStaleness, _minStalenesEffectiveTime);
    606
        }
    607
    608
        /// @inheritdoc ISuperGovernor
    609
        function executeMinStalenesChange() external {
    610
            uint256 minStalenesEffectiveTime = _minStalenesEffectiveTime;
    611 19×
            if (minStalenesEffectiveTime == 0) revert NO_PROPOSED_MIN_STALENESS();
    612 20×
            if (block.timestamp < minStalenesEffectiveTime) revert TIMELOCK_NOT_EXPIRED();
    613
    614
            _minStaleness = _proposedMinStaleness;
    615
    616
            // Reset proposal data
    617
            _proposedMinStaleness = 0;
    618
            _minStalenesEffectiveTime = 0;
    619
    620 11×
            emit MinStalenesChanged(_minStaleness);
    621
        }
    622
    623
        /*//////////////////////////////////////////////////////////////
    624
                          SUPERFORM MANAGER MANAGEMENT
    625
        //////////////////////////////////////////////////////////////*/
    626
        /// @inheritdoc ISuperGovernor
    627 0
        function addSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) {
    628 0
            if (manager == address(0)) revert INVALID_ADDRESS();
    629 0
            if (!_superformManagers.add(manager)) revert MANAGER_ALREADY_REGISTERED();
    630
    631 0
            emit SuperformManagerAdded(manager);
    632
        }
    633
    634
        /// @inheritdoc ISuperGovernor
    635 0
        function removeSuperformManager(address manager) external onlyRole(_GOVERNOR_ROLE) {
    636 0
            if (!_superformManagers.remove(manager)) revert MANAGER_NOT_REGISTERED();
    637
    638 0
            emit SuperformManagerRemoved(manager);
    639
        }
    640
    641
        /*//////////////////////////////////////////////////////////////
    642
                               VAULT HOOKS MGMT
    643
        //////////////////////////////////////////////////////////////*/
    644
    645
        /// @inheritdoc ISuperGovernor
    646 0
        function proposeVaultBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) {
    647 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    648 0
            if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT();
    649
    650 0
            uint256 effectiveTime = block.timestamp + TIMELOCK;
    651 0
            ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook];
    652 0
            data.proposedRoot = proposedRoot;
    653 0
            data.effectiveTime = effectiveTime;
    654
    655 0
            emit VaultBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime);
    656
        }
    657
    658
        /// @inheritdoc ISuperGovernor
    659 0
        function executeVaultBankHookMerkleRootUpdate(address hook) external {
    660 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    661
    662 0
            ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook];
    663
    664
            // Check if there's a proposed update
    665 0
            bytes32 proposedRoot = data.proposedRoot;
    666 0
            if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT();
    667
    668
            // Check if the effective time has passed
    669 0
            if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED();
    670
    671
            // Update the Merkle root
    672 0
            data.currentRoot = proposedRoot;
    673
    674
            // Reset the proposal
    675 0
            data.proposedRoot = bytes32(0);
    676 0
            data.effectiveTime = 0;
    677
    678 0
            emit VaultBankHookMerkleRootUpdated(hook, proposedRoot);
    679
        }
    680
    681
        /*//////////////////////////////////////////////////////////////
    682
                               SUPERBANK HOOKS MGMT
    683
        //////////////////////////////////////////////////////////////*/
    684
        /// @inheritdoc ISuperGovernor
    685 0
        function proposeSuperBankHookMerkleRoot(address hook, bytes32 proposedRoot) external onlyRole(_GOVERNOR_ROLE) {
    686 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    687 0
            if (proposedRoot == bytes32(0)) revert ZERO_PROPOSED_MERKLE_ROOT();
    688
    689 0
            uint256 effectiveTime = block.timestamp + TIMELOCK;
    690 0
            ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook];
    691 0
            data.proposedRoot = proposedRoot;
    692 0
            data.effectiveTime = effectiveTime;
    693
    694 0
            emit SuperBankHookMerkleRootProposed(hook, proposedRoot, effectiveTime);
    695
        }
    696
    697
        /// @inheritdoc ISuperGovernor
    698 0
        function executeSuperBankHookMerkleRootUpdate(address hook) external {
    699 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    700
    701 0
            ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook];
    702
    703
            // Check if there's a proposed update
    704 0
            bytes32 proposedRoot = data.proposedRoot;
    705 0
            if (proposedRoot == bytes32(0)) revert NO_PROPOSED_MERKLE_ROOT();
    706
    707
            // Check if the effective time has passed
    708 0
            if (block.timestamp < data.effectiveTime) revert TIMELOCK_NOT_EXPIRED();
    709
    710
            // Update the Merkle root
    711 0
            data.currentRoot = proposedRoot;
    712
    713
            // Reset the proposal
    714 0
            data.proposedRoot = bytes32(0);
    715 0
            data.effectiveTime = 0;
    716
    717 0
            emit SuperBankHookMerkleRootUpdated(hook, proposedRoot);
    718
        }
    719
    720
        /*//////////////////////////////////////////////////////////////
    721
                            VAULT BANK MANAGEMENT
    722
        //////////////////////////////////////////////////////////////*/
    723
        /// @inheritdoc ISuperGovernor
    724 0
        function addVaultBank(uint64 chainId, address vaultBank) external onlyRole(_GOVERNOR_ROLE) {
    725 0
            if (chainId == 0) revert INVALID_CHAIN_ID();
    726 0
            if (vaultBank == address(0)) revert INVALID_ADDRESS();
    727
    728 0
            if (_vaultBanksByChainId[chainId] != address(0)) {
    729 0
                _vaultBanks.remove(_vaultBanksByChainId[chainId]);
    730
            }
    731
    732 0
            _vaultBanks.add(vaultBank);
    733 0
            _vaultBanksByChainId[chainId] = vaultBank;
    734
    735 0
            emit VaultBankAddressAdded(chainId, vaultBank);
    736
        }
    737
    738
        /// @inheritdoc ISuperGovernor
    739 0
        function getVaultBank(uint64 chainId) external view returns (address) {
    740 0
            return _vaultBanksByChainId[chainId];
    741
        }
    742
    743
        /*//////////////////////////////////////////////////////////////
    744
                          INCENTIVE TOKEN MANAGEMENT
    745
        //////////////////////////////////////////////////////////////*/
    746
        /// @inheritdoc ISuperGovernor
    747 14×
        function proposeAddIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) {
    748 14×
            for (uint256 i; i < tokens.length; i++) {
    749 35×
                if (tokens[i] == address(0)) revert INVALID_ADDRESS();
    750 25×
                _proposedWhitelistedIncentiveTokens.add(tokens[i]);
    751
            }
    752
    753
            _proposedAddWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK;
    754
    755 17×
            emit WhitelistedIncentiveTokensProposed(
    756
                _proposedWhitelistedIncentiveTokens.values(), _proposedAddWhitelistedIncentiveTokensEffectiveTime
    757
            );
    758
        }
    759
    760
        /// @inheritdoc ISuperGovernor
    761
        function executeAddIncentiveTokens() external {
    762
            if (
    763
                _proposedAddWhitelistedIncentiveTokensEffectiveTime == 0
    764
                    || block.timestamp < _proposedAddWhitelistedIncentiveTokensEffectiveTime
    765 13×
            ) revert TIMELOCK_NOT_EXPIRED();
    766
    767
            // Get all proposed tokens before modifying the set
    768
            address[] memory tokensToAdd = _proposedWhitelistedIncentiveTokens.values();
    769
            uint256 len = tokensToAdd.length;
    770
            address token;
    771 13×
            for (uint256 i; i < len; i++) {
    772 22×
                token = tokensToAdd[i];
    773 21×
                _isWhitelistedIncentiveToken[token] = true;
    774
                // Remove from proposed whitelisted tokens
    775
                _proposedWhitelistedIncentiveTokens.remove(token);
    776
            }
    777
    778
            // Emit event once with all tokens
    779 17×
            emit WhitelistedIncentiveTokensAdded(tokensToAdd);
    780
    781
            // Reset proposal timestamp
    782
            _proposedAddWhitelistedIncentiveTokensEffectiveTime = 0;
    783
        }
    784
    785
        /// @inheritdoc ISuperGovernor
    786 0
        function proposeRemoveIncentiveTokens(address[] memory tokens) external onlyRole(_GOVERNOR_ROLE) {
    787 0
            for (uint256 i; i < tokens.length; i++) {
    788 0
                if (tokens[i] == address(0)) revert INVALID_ADDRESS();
    789 0
                if (!_isWhitelistedIncentiveToken[tokens[i]]) revert NOT_WHITELISTED_INCENTIVE_TOKEN();
    790
    791 0
                _proposedRemoveWhitelistedIncentiveTokens.add(tokens[i]);
    792
            }
    793
    794 0
            _proposedRemoveWhitelistedIncentiveTokensEffectiveTime = block.timestamp + TIMELOCK;
    795
    796 0
            emit WhitelistedIncentiveTokensProposed(
    797 0
                _proposedRemoveWhitelistedIncentiveTokens.values(), _proposedRemoveWhitelistedIncentiveTokensEffectiveTime
    798
            );
    799
        }
    800
    801
        /// @inheritdoc ISuperGovernor
    802
        function executeRemoveIncentiveTokens() external {
    803
            if (
    804
                _proposedRemoveWhitelistedIncentiveTokensEffectiveTime == 0
    805 0
                    || block.timestamp < _proposedRemoveWhitelistedIncentiveTokensEffectiveTime
    806 13×
            ) revert TIMELOCK_NOT_EXPIRED();
    807
    808
            // Get all proposed tokens before modifying the set
    809 0
            address[] memory tokensToRemove = _proposedRemoveWhitelistedIncentiveTokens.values();
    810 0
            uint256 len = tokensToRemove.length;
    811
            address token;
    812 0
            for (uint256 i; i < len; i++) {
    813 0
                token = tokensToRemove[i];
    814 0
                if (_isWhitelistedIncentiveToken[token]) {
    815 0
                    _isWhitelistedIncentiveToken[token] = false;
    816
                }
    817
                // Remove from proposed whitelisted tokens to be removed
    818 0
                _proposedRemoveWhitelistedIncentiveTokens.remove(token);
    819
            }
    820
    821
            // Emit event once with all tokens
    822 0
            emit WhitelistedIncentiveTokensRemoved(tokensToRemove);
    823
    824
            // Reset proposal timestamp
    825 0
            _proposedRemoveWhitelistedIncentiveTokensEffectiveTime = 0;
    826
        }
    827
    828
        /*//////////////////////////////////////////////////////////////
    829
                             EXTERNAL VIEW FUNCTIONS
    830
        //////////////////////////////////////////////////////////////*/
    831
        /// @inheritdoc ISuperGovernor
    832 0
        function SUPER_GOVERNOR_ROLE() external pure returns (bytes32) {
    833
            return _SUPER_GOVERNOR_ROLE;
    834
        }
    835
    836
        /// @inheritdoc ISuperGovernor
    837 0
        function GOVERNOR_ROLE() external pure returns (bytes32) {
    838
            return _GOVERNOR_ROLE;
    839
        }
    840
    841
        /// @inheritdoc ISuperGovernor
    842 0
        function BANK_MANAGER_ROLE() external pure returns (bytes32) {
    843
            return _BANK_MANAGER_ROLE;
    844
        }
    845
    846
        /// @inheritdoc ISuperGovernor
    847 0
        function GAS_MANAGER_ROLE() external pure returns (bytes32) {
    848
            return _GAS_MANAGER_ROLE;
    849
        }
    850
    851
        /// @inheritdoc ISuperGovernor
    852 0
        function GUARDIAN_ROLE() external pure returns (bytes32) {
    853
            return _GUARDIAN_ROLE;
    854
        }
    855
    856
        /// @inheritdoc ISuperGovernor
    857 0
        function SUPER_ASSET_FACTORY() external pure returns (bytes32) {
    858
            return _SUPER_ASSET_FACTORY;
    859
        }
    860
    861
        /// @inheritdoc ISuperGovernor
    862 80×
        function getAddress(bytes32 key) external view returns (address) {
    863 60×
            address value = _addressRegistry[key];
    864 10×
            if (value == address(0)) revert CONTRACT_NOT_FOUND();
    865
            return value;
    866
        }
    867
    868
        /// @inheritdoc ISuperGovernor
    869 0
        function isManagerTakeoverFrozen() external view returns (bool) {
    870 0
            return _managerTakeoversFrozen;
    871
        }
    872
    873
        /// @inheritdoc ISuperGovernor
    874 24×
        function isHookRegistered(address hook) external view returns (bool) {
    875 10×
            return _registeredHooks.contains(hook);
    876
        }
    877
    878
        /// @inheritdoc ISuperGovernor
    879 24×
        function isFulfillRequestsHookRegistered(address hook) external view returns (bool) {
    880 10×
            return _registeredFulfillRequestsHooks.contains(hook);
    881
        }
    882
    883
        /// @inheritdoc ISuperGovernor
    884 0
        function getRegisteredHooks() external view returns (address[] memory) {
    885 0
            return _registeredHooks.values();
    886
        }
    887
    888
        /// @inheritdoc ISuperGovernor
    889 0
        function getRegisteredFulfillRequestsHooks() external view returns (address[] memory) {
    890 0
            return _registeredFulfillRequestsHooks.values();
    891
        }
    892
    893
        /// @inheritdoc ISuperGovernor
    894 0
        function isValidator(address validator) external view returns (bool) {
    895 0
            return _validators.contains(validator);
    896
        }
    897
    898
        /// @inheritdoc ISuperGovernor
    899 0
        function isGuardian(address guardian) external view returns (bool) {
    900 0
            return hasRole(_GUARDIAN_ROLE, guardian);
    901
        }
    902
    903
        /// @inheritdoc ISuperGovernor
    904 0
        function isRelayer(address relayer) external view returns (bool) {
    905 0
            return _relayers.contains(relayer);
    906
        }
    907
    908
        /// @inheritdoc ISuperGovernor
    909 0
        function isExecutor(address executor) external view returns (bool) {
    910 0
            return _executors.contains(executor);
    911
        }
    912
    913
        /// @inheritdoc ISuperGovernor
    914 0
        function getValidators() external view returns (address[] memory) {
    915 0
            return _validators.values();
    916
        }
    917
    918
        /// @inheritdoc ISuperGovernor
    919 0
        function getRelayers() external view returns (address[] memory) {
    920 0
            return _relayers.values();
    921
        }
    922
    923
        /// @inheritdoc ISuperGovernor
    924 0
        function getExecutors() external view returns (address[] memory) {
    925 0
            return _executors.values();
    926
        }
    927
    928
        /// @inheritdoc ISuperGovernor
    929 0
        function getProposedActivePPSOracle() external view returns (address proposedOracle, uint256 effectiveTime) {
    930 0
            return (_proposedActivePPSOracle, _activePPSOracleEffectiveTime);
    931
        }
    932
    933
        /// @inheritdoc ISuperGovernor
    934 0
        function getPPSOracleQuorum() external view returns (uint256) {
    935 0
            return _activePPSOracleQuorum;
    936
        }
    937
    938
        /// @inheritdoc ISuperGovernor
    939 0
        function getActivePPSOracle() external view returns (address) {
    940 0
            if (_activePPSOracle == address(0)) revert NO_ACTIVE_PPS_ORACLE();
    941 0
            return _activePPSOracle;
    942
        }
    943
    944
        /// @inheritdoc ISuperGovernor
    945
        function isActivePPSOracle(address oracle) external view returns (bool) {
    946
            return oracle == _activePPSOracle;
    947
        }
    948
    949
        /// @inheritdoc ISuperGovernor
    950 20×
        function getFee(FeeType feeType) external view returns (uint256) {
    951 0
            return _feeValues[feeType];
    952
        }
    953
    954
        /// @inheritdoc ISuperGovernor
    955 0
        function getGasInfo(address oracle_) external view returns (GasInfo memory) {
    956 0
            return _oracleGasInfo[oracle_];
    957
        }
    958
    959
         /// @inheritdoc ISuperGovernor
    960 14×
        function getUpkeepCostPerBatchUpdate(address oracle_, uint256 chargeableEntries_) external view returns (uint256) {
    961
            // Calculate total gas cost
    962 21×
            uint256 totalGas = _oracleGasInfo[oracle_].baseGasBatch + 
    963 21×
                (_oracleGasInfo[oracle_].gasIncreasePerEntryBatch * chargeableEntries_);
    964
    965
            return _convertGasToUp(totalGas);
    966
        }
    967
    968
        /// @inheritdoc ISuperGovernor
    969
        function getMinStaleness() external view returns (uint256) {
    970
            return _minStaleness;
    971
        }
    972
    973
        /// @inheritdoc ISuperGovernor
    974 0
        function getProposedMinStaleness() external view returns (uint256 proposedMinStaleness, uint256 effectiveTime) {
    975 0
            return (_proposedMinStaleness, _minStalenesEffectiveTime);
    976
        }
    977
    978
        /// @inheritdoc ISuperGovernor
    979 0
        function getSuperBankHookMerkleRoot(address hook) external view returns (bytes32) {
    980 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    981 0
            return superBankHooksMerkleRoots[hook].currentRoot;
    982
        }
    983
    984
        /// @inheritdoc ISuperGovernor
    985 0
        function getVaultBankHookMerkleRoot(address hook) external view returns (bytes32) {
    986 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    987 0
            return vaultBankHooksMerkleRoots[hook].currentRoot;
    988
        }
    989
    990
        /// @inheritdoc ISuperGovernor
    991 0
        function getProposedSuperBankHookMerkleRoot(address hook)
    992
            external
    993
            view
    994 0
            returns (bytes32 proposedRoot, uint256 effectiveTime)
    995
        {
    996 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    997 0
            ISuperGovernor.HookMerkleRootData storage data = superBankHooksMerkleRoots[hook];
    998 0
            return (data.proposedRoot, data.effectiveTime);
    999
        }
    1000
    1001
        /// @inheritdoc ISuperGovernor
    1002 0
        function getProposedVaultBankHookMerkleRoot(address hook)
    1003
            external
    1004
            view
    1005 0
            returns (bytes32 proposedRoot, uint256 effectiveTime)
    1006
        {
    1007 0
            if (!_registeredHooks.contains(hook)) revert HOOK_NOT_APPROVED();
    1008 0
            ISuperGovernor.HookMerkleRootData storage data = vaultBankHooksMerkleRoots[hook];
    1009 0
            return (data.proposedRoot, data.effectiveTime);
    1010
        }
    1011
    1012
        /// @inheritdoc ISuperGovernor
    1013 0
        function getProver() external view returns (address) {
    1014 0
            return _prover;
    1015
        }
    1016
    1017
        /// @inheritdoc ISuperGovernor
    1018
        function isUpkeepPaymentsEnabled() external view returns (bool enabled) {
    1019
            return _upkeepPaymentsEnabled;
    1020
        }
    1021
    1022
        /// @inheritdoc ISuperGovernor
    1023 0
        function getProposedUpkeepPaymentsStatus() external view returns (bool enabled, uint256 effectiveTime) {
    1024 0
            return (_proposedUpkeepPaymentsEnabled, _upkeepPaymentsChangeEffectiveTime);
    1025
        }
    1026
    1027
        /// @inheritdoc ISuperGovernor
    1028 12×
        function isSuperformManager(address manager) external view returns (bool isSuperform) {
    1029
            return _superformManagers.contains(manager);
    1030
        }
    1031
    1032
        /// @inheritdoc ISuperGovernor
    1033 0
        function getAllSuperformManagers() external view returns (address[] memory managers) {
    1034 0
            return _superformManagers.values();
    1035
        }
    1036
    1037
        /// @inheritdoc ISuperGovernor
    1038 0
        function getManagersPaginated(
    1039
            uint256 cursor,
    1040
            uint256 limit
    1041
        )
    1042
            external
    1043
            view
    1044 0
            returns (address[] memory chunkOfManagers, uint256 next)
    1045 0
        {
    1046 0
            uint256 len = _superformManagers.length();
    1047
    1048
            // clamp limit so we don’t read past end
    1049 0
            uint256 realLimit = limit;
    1050
            // If cursor is beyond the end, return empty array
    1051 0
            if (cursor >= len) {
    1052 0
                return (new address[](0), 0);
    1053
            }
    1054
    1055 0
            uint256 remaining = len - cursor;
    1056 0
            if (realLimit > remaining) realLimit = remaining;
    1057
    1058 0
            chunkOfManagers = new address[](realLimit);
    1059 0
            for (uint256 i; i < realLimit; ++i) {
    1060 0
                chunkOfManagers[i] = _superformManagers.at(cursor + i);
    1061
            }
    1062
    1063 0
            next = (cursor + realLimit < len) ? cursor + realLimit : 0;
    1064
        }
    1065
    1066
        /// @inheritdoc ISuperGovernor
    1067 0
        function getSuperformManagersCount() external view returns (uint256) {
    1068 0
            return _superformManagers.length();
    1069
        }
    1070
    1071
        /// @inheritdoc ISuperGovernor
    1072 0
        function isWhitelistedIncentiveToken(address token) external view returns (bool) {
    1073 0
            return _isWhitelistedIncentiveToken[token];
    1074
        }
    1075
    1076
        /// @inheritdoc ISuperGovernor
    1077 0
        function registerProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) {
    1078 0
            if (keeper == address(0)) revert INVALID_ADDRESS();
    1079 0
            if (_protectedKeepers.contains(keeper)) revert KEEPER_ALREADY_REGISTERED();
    1080
    1081 0
            _protectedKeepers.add(keeper);
    1082 0
            emit ProtectedKeeperRegistered(keeper);
    1083
        }
    1084
    1085
        /// @inheritdoc ISuperGovernor
    1086 0
        function unregisterProtectedKeeper(address keeper) external onlyRole(_GOVERNOR_ROLE) {
    1087 0
            if (!_protectedKeepers.contains(keeper)) revert KEEPER_NOT_REGISTERED();
    1088
    1089 0
            _protectedKeepers.remove(keeper);
    1090 0
            emit ProtectedKeeperUnregistered(keeper);
    1091
        }
    1092
    1093
        /// @inheritdoc ISuperGovernor
    1094 12×
        function isProtectedKeeper(address keeper) external view returns (bool) {
    1095
            return _protectedKeepers.contains(keeper);
    1096
        }
    1097
    1098
        /// @inheritdoc ISuperGovernor
    1099 0
        function getProtectedKeepers() external view returns (address[] memory) {
    1100 0
            return _protectedKeepers.values();
    1101
        }
    1102
    1103
        /// @inheritdoc ISuperGovernor
    1104 0
        function getProtectedKeepersCount() external view returns (uint256) {
    1105 0
            return _protectedKeepers.length();
    1106
        }
    1107
    1108
        /*//////////////////////////////////////////////////////////////
    1109
                               INTERNAL FUNCTIONS
    1110
        //////////////////////////////////////////////////////////////*/
    1111
        function _convertGasToUp(uint256 gasAmount) internal view returns (uint256) {
    1112
            address oracle = _addressRegistry[SUPER_ORACLE];
    1113 15×
            if (oracle == address(0)) revert SUPER_ORACLE_NOT_FOUND();
    1114 0
            address upToken = _addressRegistry[UP];
    1115 0
            if (upToken == address(0)) revert UP_NOT_FOUND();
    1116
    1117
            // Step 1: convert gas to ETH
    1118 0
            (uint256 ethAmount,,,) = ISuperOracle(oracle).getQuoteFromProvider(
    1119
                gasAmount,
    1120
                GAS_QUOTE,
    1121
                GWEI_QUOTE,
    1122
                AVERAGE_PROVIDER
    1123
            );
    1124
    1125
            // Step 2: convert ETH to USD
    1126 0
            (uint256 ethToUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider(
    1127
                ethAmount,
    1128
                NATIVE_TOKEN,
    1129
                USD_TOKEN,
    1130
                AVERAGE_PROVIDER
    1131
            );
    1132
    1133
            // Step 3: convert USD to UP (how much USD per UP token)
    1134 0
            (uint256 upPerUsd,,,) = ISuperOracle(oracle).getQuoteFromProvider(
    1135 0
                1e18, // 1 UP token (18 decimals)
    1136
                upToken,
    1137
                USD_TOKEN,
    1138
                AVERAGE_PROVIDER
    1139
            );
    1140
    1141
            // Calculate required UP tokens
    1142
            // usdAmount / upPerUsd = required UP tokens
    1143 0
            uint256 requiredUpTokens = Math.mulDiv(ethToUsd, 1e18, upPerUsd, Math.Rounding.Ceil);
    1144
            return requiredUpTokens;
    1145
        }
    1146
    }
    86% src/SuperVault/SuperVault.sol
    Lines covered: 167 / 192 (86%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // External
    5
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    6
    import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
    7
    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    8
    9
    // OpenZeppelin Upgradeable
    10
    import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    11
    import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
    12
    import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
    13
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    14
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    15
    import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";
    16
    import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
    17
    import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
    18
    19
    // Interfaces
    20
    import { ISuperVault } from "../interfaces/SuperVault/ISuperVault.sol";
    21
    import { ISuperVaultStrategy } from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
    22
    import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
    23
    import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
    24
    import { IERC7540Operator, IERC7540Redeem, IERC7741 } from "../vendor/standards/ERC7540/IERC7540Vault.sol";
    25
    import { IERC7575 } from "../vendor/standards/ERC7575/IERC7575.sol";
    26
    import { ISuperVaultEscrow } from "../interfaces/SuperVault/ISuperVaultEscrow.sol";
    27
    28
    // Libraries
    29
    import { AssetMetadataLib } from "../libraries/AssetMetadataLib.sol";
    30
    31
    /// @title SuperVault
    32
    /// @author Superform Labs
    33
    /// @notice SuperVault vault contract implementing ERC4626 with synchronous deposits and asynchronous redeems via
    34
    /// ERC7540
    35 927×
    contract SuperVault is
    36
        Initializable,
    37
        ERC20Upgradeable,
    38
        IERC7540Redeem,
    39
        IERC7741,
    40
        IERC4626,
    41
        ISuperVault,
    42
        ReentrancyGuardUpgradeable,
    43
        EIP712Upgradeable
    44
    {
    45
        using AssetMetadataLib for address;
    46
        using SafeERC20 for IERC20;
    47
        using Math for uint256;
    48
    49
        /*//////////////////////////////////////////////////////////////
    50
                                    CONSTANTS
    51
        //////////////////////////////////////////////////////////////*/
    52
        uint256 private constant REQUEST_ID = 0;
    53
        uint256 private constant BPS_PRECISION = 10_000;
    54
    55
        // EIP712 TypeHash
    56 0
        bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH =
    57 0
            keccak256("AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)");
    58
    59
        /*//////////////////////////////////////////////////////////////
    60
                                    STATE
    61
        //////////////////////////////////////////////////////////////*/
    62 16×
        address public share;
    63
        IERC20 private _asset;
    64
        uint8 private _underlyingDecimals;
    65 0
        ISuperVaultStrategy public strategy;
    66 0
        address public escrow;
    67 12×
        uint256 public PRECISION;
    68
    69
        // Core contracts
    70 12×
        ISuperGovernor public immutable superGovernor;
    71
    72
        /// @inheritdoc IERC7540Operator
    73 0
        mapping(address owner => mapping(address operator => bool)) public isOperator;
    74
    75
        // Authorization tracking
    76
        mapping(address controller => mapping(bytes32 nonce => bool used)) private _authorizations;
    77
    78
        /*//////////////////////////////////////////////////////////////
    79
                                CONSTRUCTOR
    80
        //////////////////////////////////////////////////////////////*/
    81
    82 28×
        constructor(address superGovernor_) {
    83
            if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
    84
            superGovernor = ISuperGovernor(superGovernor_);
    85
            emit SuperGovernorSet(superGovernor_);
    86
    87
            _disableInitializers();
    88
        }
    89
    90
        /*//////////////////////////////////////////////////////////////
    91
                                INITIALIZATION
    92
        //////////////////////////////////////////////////////////////*/
    93
    94
        /// @notice Initialize the vault with required parameters
    95
        /// @param asset_ The underlying asset token address
    96
        /// @param name_ The name of the vault token
    97
        /// @param symbol_ The symbol of the vault token
    98
        /// @param strategy_ The strategy contract address
    99
        /// @param escrow_ The escrow contract address
    100 17×
        function initialize(
    101
            address asset_,
    102
            string memory name_,
    103
            string memory symbol_,
    104
            address strategy_,
    105
            address escrow_
    106
        )
    107
            external
    108
            initializer
    109
        {
    110
            if (asset_ == address(0)) revert INVALID_ASSET();
    111
            if (strategy_ == address(0)) revert INVALID_STRATEGY();
    112
            if (escrow_ == address(0)) revert INVALID_ESCROW();
    113
    114
            // Initialize parent contracts
    115
            __ERC20_init(name_, symbol_);
    116
            __ReentrancyGuard_init();
    117 21×
            __EIP712_init(name_, "1");
    118
    119
            // Set asset and precision
    120 12×
            _asset = IERC20(asset_);
    121
            (bool success, uint8 assetDecimals) = asset_.tryGetAssetDecimals();
    122 17×
            if (!success) revert INVALID_ASSET();
    123 18×
            _underlyingDecimals = assetDecimals;
    124 10×
            PRECISION = 10 ** _underlyingDecimals;
    125 11×
            share = address(this);
    126 15×
            strategy = ISuperVaultStrategy(strategy_);
    127 12×
            escrow = escrow_;
    128
        }
    129
    130
        /*//////////////////////////////////////////////////////////////
    131
                                ERC20 OVERRIDES
    132
        //////////////////////////////////////////////////////////////*/
    133
    134
        /*//////////////////////////////////////////////////////////////
    135
                            USER EXTERNAL FUNCTIONS
    136
        //////////////////////////////////////////////////////////////*/
    137
    138
        /// @inheritdoc IERC4626
    139 24×
        function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) {
    140 36×
            if (receiver == address(0)) revert ZERO_ADDRESS();
    141 25×
            if (assets == 0) revert ZERO_AMOUNT();
    142
    143
            // Forward assets from msg-sender to strategy
    144 34×
            _asset.safeTransferFrom(msg.sender, address(strategy), assets);
    145
    146
            // Single executor call: strategy skims entry fee, accounts on NET, returns net shares
    147 138×
            shares = strategy.handleOperations4626Deposit(receiver, assets);
    148 12×
            if (shares == 0) revert ZERO_AMOUNT();
    149
    150
            // Mint the net shares
    151 12×
            _mint(receiver, shares);
    152
    153 34×
            emit Deposit(msg.sender, receiver, assets, shares);
    154
        }
    155
    156
        /// @inheritdoc IERC4626
    157 26×
        function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256 assets) {
    158 36×
            if (receiver == address(0)) revert ZERO_ADDRESS();
    159 25×
            if (shares == 0) revert ZERO_AMOUNT();
    160
    161
            uint256 assetsNet;
    162 138×
            (assets, assetsNet) = strategy.quoteMintAssetsGross(shares);
    163
    164
            // Forward quoted gross assets from msg-sender to strategy
    165 32×
            _asset.safeTransferFrom(msg.sender, address(strategy), assets);
    166
    167
            // Single executor call: strategy handles fees and accounts on NET
    168 104×
            strategy.handleOperations4626Mint(receiver, shares, assets, assetsNet);
    169
    170
            // Mint the exact shares asked
    171 12×
            _mint(receiver, shares);
    172
    173 34×
            emit Deposit(msg.sender, receiver, assets, shares);
    174
        }
    175
    176
        /// @inheritdoc IERC7540Redeem
    177
        /// @notice Once owner has authorized an operator, the operator can request a redeem with any controller address
    178 18×
        function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256) {
    179 19×
            if (shares == 0) revert ZERO_AMOUNT();
    180 14×
            if (owner == address(0) || controller == address(0)) revert ZERO_ADDRESS();
    181 14×
            if (owner != msg.sender && !isOperator[owner][msg.sender]) revert INVALID_OWNER_OR_OPERATOR();
    182 24×
            if (balanceOf(owner) < shares) revert INVALID_AMOUNT();
    183
    184
            // Enforce auditor's invariant for current accounting model
    185
            if (controller != owner) revert CONTROLLER_MUST_EQUAL_OWNER();
    186
    187
            // Transfer shares to escrow for temporary locking
    188 11×
            _approve(owner, escrow, shares);
    189 41×
            ISuperVaultEscrow(escrow).escrowShares(owner, shares);
    190
    191
            // Forward to strategy (7540 path)
    192 60×
            strategy.handleOperations7540(ISuperVaultStrategy.Operation.RedeemRequest, controller, address(0), shares);
    193
    194 20×
            emit RedeemRequest(controller, owner, REQUEST_ID, msg.sender, shares);
    195
            return REQUEST_ID;
    196
        }
    197
    198
        /// @inheritdoc ISuperVault
    199 14×
        function cancelRedeem(address controller) external {
    200
            _validateController(controller);
    201
    202 61×
            uint256 shares = strategy.pendingRedeemRequest(controller);
    203
    204
            // Forward to strategy (7540 path)
    205 57×
            strategy.handleOperations7540(ISuperVaultStrategy.Operation.CancelRedeem, controller, address(0), 0);
    206
    207
            // Return shares to controller
    208 41×
            ISuperVaultEscrow(escrow).returnShares(controller, shares);
    209
    210 12×
            emit RedeemRequestCancelled(controller, msg.sender);
    211
        }
    212
    213
        /// @inheritdoc IERC7540Operator
    214 17×
        function setOperator(address operator, bool approved) external returns (bool success) {
    215 20×
            if (msg.sender == operator) revert UNAUTHORIZED();
    216 41×
            isOperator[msg.sender][operator] = approved;
    217 11×
            emit OperatorSet(msg.sender, operator, approved);
    218
            return true;
    219
        }
    220
    221
        /// @inheritdoc IERC7741
    222 0
        function authorizeOperator(
    223
            address controller,
    224
            address operator,
    225
            bool approved,
    226
            bytes32 nonce,
    227
            uint256 deadline,
    228
            bytes memory signature
    229
        )
    230
            external
    231 0
            returns (bool)
    232
        {
    233 0
            if (controller == operator) revert UNAUTHORIZED();
    234 0
            if (block.timestamp > deadline) revert DEADLINE_PASSED();
    235 0
            if (_authorizations[controller][nonce]) revert UNAUTHORIZED();
    236
    237 0
            _authorizations[controller][nonce] = true;
    238
    239 0
            bytes32 structHash =
    240 0
                keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline));
    241 0
            bytes32 digest = _hashTypedDataV4(structHash);
    242
    243 0
            if (!_isValidSignature(controller, digest, signature)) revert INVALID_SIGNATURE();
    244
    245 0
            isOperator[controller][operator] = approved;
    246 0
            emit OperatorSet(controller, operator, approved);
    247
    248 0
            return true;
    249
        }
    250
    251
        /*//////////////////////////////////////////////////////////////
    252
                        USER EXTERNAL VIEW FUNCTIONS
    253
        //////////////////////////////////////////////////////////////*/
    254
    255
        //--ERC7540--
    256
    257
        /// @inheritdoc IERC7540Redeem
    258 22×
        function pendingRedeemRequest(
    259
            uint256, /*requestId*/
    260
            address controller
    261
        )
    262
            external
    263
            view
    264
            returns (uint256 pendingShares)
    265
        {
    266 112×
            return strategy.pendingRedeemRequest(controller);
    267
        }
    268
    269
        /// @inheritdoc IERC7540Redeem
    270 22×
        function claimableRedeemRequest(
    271
            uint256, /*requestId*/
    272
            address controller
    273
        )
    274
            external
    275
            view
    276
            returns (uint256 claimableShares)
    277
        {
    278
            return maxRedeem(controller);
    279
        }
    280
    281
        //--Operator Management--
    282
    283
        /// @inheritdoc IERC7741
    284 0
        function authorizations(address controller, bytes32 nonce) external view returns (bool used) {
    285 0
            return _authorizations[controller][nonce];
    286
        }
    287
    288
        /// @inheritdoc IERC7741
    289
        function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
    290
            return _domainSeparatorV4();
    291
        }
    292
    293
        /// @inheritdoc IERC7741
    294 13×
        function invalidateNonce(bytes32 nonce) external {
    295 42×
            if (_authorizations[msg.sender][nonce]) revert INVALID_NONCE();
    296 33×
            _authorizations[msg.sender][nonce] = true;
    297
    298
            emit NonceInvalidated(msg.sender, nonce);
    299
        }
    300
    301
        /*//////////////////////////////////////////////////////////////
    302
                                ERC4626 IMPLEMENTATION
    303
        //////////////////////////////////////////////////////////////*/
    304 16×
        function decimals() public view virtual override(ERC20Upgradeable, IERC20Metadata) returns (uint8) {
    305 24×
            return _underlyingDecimals;
    306
        }
    307
    308
        function asset() public view virtual override returns (address) {
    309
            return address(_asset);
    310
        }
    311
    312
        /// @inheritdoc IERC4626
    313 59×
        function totalAssets() external view override returns (uint256) {
    314
            uint256 supply = totalSupply();
    315 10×
            if (supply == 0) return 0;
    316
            uint256 currentPPS = _getStoredPPS();
    317 13×
            return Math.mulDiv(supply, currentPPS, PRECISION, Math.Rounding.Floor);
    318
        }
    319
    320
        /// @inheritdoc IERC4626
    321 14×
        function convertToShares(uint256 assets) public view override returns (uint256) {
    322
            uint256 pps = _getStoredPPS();
    323
            if (pps == 0) return 0;
    324
            return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor);
    325
        }
    326
    327
        /// @inheritdoc IERC4626
    328 34×
        function convertToAssets(uint256 shares) public view override returns (uint256) {
    329 14×
            uint256 currentPPS = _getStoredPPS();
    330 16×
            if (currentPPS == 0) return 0;
    331 22×
            return Math.mulDiv(shares, currentPPS, PRECISION, Math.Rounding.Floor);
    332
        }
    333
    334
        /// @inheritdoc IERC4626
    335 32×
        function maxDeposit(address) public view override returns (uint256) {
    336 20×
            if (_isPaused()) return 0;
    337
            return type(uint256).max;
    338
        }
    339
    340
        /// @inheritdoc IERC4626
    341
        function maxMint(address) external view override returns (uint256) {
    342
            if (_isPaused()) return 0;
    343
            return type(uint256).max;
    344
        }
    345
    346
        /// @inheritdoc IERC4626
    347 26×
        function maxWithdraw(address owner) public view override returns (uint256) {
    348 112×
            return strategy.claimableWithdraw(owner);
    349
        }
    350
    351
        /// @inheritdoc IERC4626
    352 34×
        function maxRedeem(address owner) public view override returns (uint256) {
    353 120×
            uint256 withdrawPrice = strategy.getAverageWithdrawPrice(owner);
    354 15×
            if (withdrawPrice == 0) return 0;
    355 15×
            return maxWithdraw(owner).mulDiv(PRECISION, withdrawPrice, Math.Rounding.Floor);
    356
        }
    357
    358
        /// @inheritdoc IERC4626
    359 36×
        function previewDeposit(uint256 assets) public view override returns (uint256) {
    360 14×
            uint256 pps = _getStoredPPS();
    361 16×
            if (pps == 0) return 0;
    362
    363 16×
            (uint256 feeBps,) = _getManagementFeeConfig();
    364
    365 32×
            if (feeBps == 0) return Math.mulDiv(assets, PRECISION, pps, Math.Rounding.Floor);
    366
            // fee-on-gross: fee = ceil(gross * feeBps / BPS)
    367 20×
            uint256 fee = Math.mulDiv(assets, feeBps, BPS_PRECISION, Math.Rounding.Ceil);
    368
    369 16×
            uint256 assetsNet = assets - fee;
    370 20×
            return Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor);
    371
        }
    372
    373
        /// @inheritdoc IERC4626
    374 40×
        function previewMint(uint256 shares) public view override returns (uint256) {
    375 14×
            uint256 pps = _getStoredPPS();
    376 16×
            if (pps == 0) return 0;
    377
    378 24×
            uint256 assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil);
    379
    380 16×
            (uint256 feeBps,) = _getManagementFeeConfig();
    381 14×
            if (feeBps == 0) return assetsNet;
    382 14×
            if (feeBps >= BPS_PRECISION) return 0; // impossible to mint (would require infinite gross)
    383
    384 24×
            return Math.mulDiv(assetsNet, BPS_PRECISION, (BPS_PRECISION - feeBps), Math.Rounding.Ceil);
    385
        }
    386
    387
        /// @inheritdoc IERC4626
    388 12×
        function previewWithdraw(uint256 /* assets*/ ) public pure override returns (uint256) {
    389 13×
            revert NOT_IMPLEMENTED();
    390
        }
    391
    392
        /// @inheritdoc IERC4626
    393
        function previewRedeem(uint256 /* shares*/ ) public pure override returns (uint256) {
    394
            revert NOT_IMPLEMENTED();
    395
        }
    396
    397
        /// @inheritdoc IERC4626
    398 22×
        function withdraw(
    399
            uint256 assets,
    400
            address receiver,
    401
            address controller
    402
        )
    403
            public
    404
            override
    405
            nonReentrant
    406
            returns (uint256 shares)
    407
        {
    408 23×
            if (receiver == address(0)) revert ZERO_ADDRESS();
    409 10×
            _validateController(controller);
    410
    411 122×
            uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller);
    412 37×
            if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE();
    413
    414
            uint256 maxWithdrawAmount = maxWithdraw(controller);
    415
            if (assets > maxWithdrawAmount) revert INVALID_AMOUNT();
    416
    417
            // Calculate shares based on assets and average withdraw price
    418 13×
            shares = assets.mulDiv(PRECISION, averageWithdrawPrice, Math.Rounding.Floor);
    419
    420
            // Take assets from strategy (7540 path)
    421 51×
            strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets);
    422
    423 22×
            emit Withdraw(msg.sender, receiver, controller, assets, shares);
    424
        }
    425
    426
        /// @inheritdoc IERC4626
    427 22×
        function redeem(
    428
            uint256 shares,
    429
            address receiver,
    430
            address controller
    431
        )
    432
            public
    433
            override
    434
            nonReentrant
    435
            returns (uint256 assets)
    436
        {
    437 23×
            if (receiver == address(0)) revert ZERO_ADDRESS();
    438 10×
            _validateController(controller);
    439
    440 122×
            uint256 averageWithdrawPrice = strategy.getAverageWithdrawPrice(controller);
    441 37×
            if (averageWithdrawPrice == 0) revert INVALID_WITHDRAW_PRICE();
    442
    443
            // Calculate assets based on shares and average withdraw price
    444 14×
            assets = shares.mulDiv(averageWithdrawPrice, PRECISION, Math.Rounding.Floor);
    445
    446
            uint256 maxWithdrawAmount = maxWithdraw(controller);
    447
            if (assets > maxWithdrawAmount) revert INVALID_AMOUNT();
    448
    449
            // Take assets from strategy (7540 path)
    450 53×
            strategy.handleOperations7540(ISuperVaultStrategy.Operation.ClaimRedeem, controller, receiver, assets);
    451
    452 15×
            emit Withdraw(msg.sender, receiver, controller, assets, shares);
    453
        }
    454
    455
        // @inheritdoc ISuperVault
    456 32×
        function burnShares(uint256 amount) external {
    457 28×
            if (msg.sender != address(strategy)) revert UNAUTHORIZED();
    458 13×
            _burn(escrow, amount);
    459
        }
    460
    461
        // @inheritdoc ISuperVault
    462 24×
        function onRedeemClaimable(
    463
            address user,
    464
            uint256 assets,
    465
            uint256 shares,
    466
            uint256 averageWithdrawPrice,
    467
            uint256 accumulatorShares,
    468
            uint256 accumulatorCostBasis
    469
        )
    470
            external
    471
        {
    472
            if (msg.sender != address(strategy)) revert UNAUTHORIZED();
    473 15×
            emit RedeemClaimable(
    474
                user, REQUEST_ID, assets, shares, averageWithdrawPrice, accumulatorShares, accumulatorCostBasis
    475
            );
    476
        }
    477
    478
        /*//////////////////////////////////////////////////////////////
    479
                                ERC165 INTERFACE
    480
        //////////////////////////////////////////////////////////////*/
    481 20×
        function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
    482
            return interfaceId == type(IERC7540Redeem).interfaceId || interfaceId == type(IERC165).interfaceId
    483 0
                || interfaceId == type(IERC7741).interfaceId || interfaceId == type(IERC4626).interfaceId
    484 0
                || interfaceId == type(IERC7575).interfaceId || interfaceId == type(IERC7540Operator).interfaceId;
    485
        }
    486
    487
        /*//////////////////////////////////////////////////////////////
    488
                            INTERNAL FUNCTIONS
    489
        //////////////////////////////////////////////////////////////*/
    490
        /// @notice Validates that the controller is the msg.sender or has been authorized by the controller
    491
        /// @param controller The controller to validate
    492
        function _validateController(address controller) internal view {
    493 106×
            if (controller != msg.sender && !isOperator[controller][msg.sender]) revert INVALID_CONTROLLER();
    494
        }
    495
    496
        /// @notice Verify an EIP712 signature using OpenZeppelin's ECDSA library
    497
        /// @param signer The signer to verify
    498
        /// @param digest The digest to verify
    499
        /// @param signature The signature to verify
    500 0
        function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal pure returns (bool) {
    501 0
            address recoveredSigner = ECDSA.recover(digest, signature);
    502 0
            return recoveredSigner == signer;
    503
        }
    504
    505
        /// @notice Overrides the ERC20 _update function to update the state of the vault when a transfer occurs
    506
        /// @param from The address of the sender
    507
        /// @param to The address of the recipient
    508
        /// @param value The amount of shares being transferred
    509
        function _update(address from, address to, uint256 value) internal override(ERC20Upgradeable) {
    510
            /// @dev Move only accumulators pro-rata between actual users, not to/from infrastructure contracts
    511 183×
            if (from != address(0) && to != address(0) && to != address(escrow) && from != address(escrow)) {
    512
                uint256 shares = value;
    513
                // Zero-value transfers are legal: treat as accounting no-op.
    514 15×
                if (shares > 0) {
    515 135×
                    strategy.moveAccumulatorOnTransfer(from, to, shares);
    516
                }
    517
            }
    518 24×
            super._update(from, to, value);
    519
        }
    520
    521
        function _getStoredPPS() internal view returns (uint256) {
    522 126×
            return strategy.getStoredPPS();
    523
        }
    524
    525
        /// @notice Checks if the vault is currently paused
    526
        /// @dev This accesses the SuperVaultAggregator directly via the governor to determine pause status
    527
        /// @return True if the vault is paused, false otherwise
    528
        function _isPaused() internal view returns (bool) {
    529 244×
            address aggregatorAddress = superGovernor.getAddress(superGovernor.SUPER_VAULT_AGGREGATOR());
    530 128×
            return ISuperVaultAggregator(aggregatorAddress).isStrategyPaused(address(strategy));
    531
        }
    532
    533
        /// @dev Read management fee config (view-only for previews)
    534 12×
        function _getManagementFeeConfig() internal view returns (uint256 feeBps, address recipient) {
    535 140×
            ISuperVaultStrategy.FeeConfig memory cfg = strategy.getConfigInfo();
    536 26×
            return (cfg.managementFeeBps, cfg.recipient);
    537
        }
    538
    }
    56% src/SuperVault/SuperVaultAggregator.sol
    Lines covered: 353 / 621 (56%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // External
    5
    import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
    6
    import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    7
    import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    8
    import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
    9
    import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    10
    import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
    11
    12
    // Superform
    13
    import {SuperVault} from "./SuperVault.sol";
    14
    import {SuperVaultStrategy} from "./SuperVaultStrategy.sol";
    15
    import {SuperVaultEscrow} from "./SuperVaultEscrow.sol";
    16
    import {ISuperGovernor} from "../interfaces/ISuperGovernor.sol";
    17
    import {ISuperVaultAggregator} from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
    18
    // Libraries
    19
    import {AssetMetadataLib} from "../libraries/AssetMetadataLib.sol";
    20
    21
    /// @title SuperVaultAggregator
    22
    /// @author Superform Labs
    23
    /// @notice Registry and PPS oracle for all SuperVaults
    24
    /// @dev Creates new SuperVault trios and manages PPS updates
    25 0
    contract SuperVaultAggregator is ISuperVaultAggregator {
    26
        using AssetMetadataLib for address;
    27
        using Clones for address;
    28
        using SafeERC20 for IERC20;
    29
        using Math for uint256;
    30
        using EnumerableSet for EnumerableSet.AddressSet;
    31
    32
        /*//////////////////////////////////////////////////////////////
    33
                                     STORAGE
    34
        //////////////////////////////////////////////////////////////*/
    35
        // Vault implementation contracts
    36 0
        address public immutable VAULT_IMPLEMENTATION;
    37 0
        address public immutable STRATEGY_IMPLEMENTATION;
    38 0
        address public immutable ESCROW_IMPLEMENTATION;
    39
    40
        // Governance
    41 0
        ISuperGovernor public immutable SUPER_GOVERNOR;
    42
    43
        // Claimable upkeep
    44 66×
        uint256 public claimableUpkeep;
    45
    46
        // Strategy data storage
    47
        mapping(address strategy => StrategyData) private _strategyData;
    48
    49
        // Upkeep balances
    50
        mapping(address manager => uint256 upkeep) private _managerUpkeepBalance;
    51
    52
        // Stake balances
    53
        mapping(address manager => uint256 stake) private _managerStakeBalance;
    54
    55
        // Registry of created vaults
    56
        EnumerableSet.AddressSet private _superVaults;
    57
        EnumerableSet.AddressSet private _superVaultStrategies;
    58
        EnumerableSet.AddressSet private _superVaultEscrows;
    59
    60
        // Constant for PPS decimals
    61 0
        uint256 public constant PPS_DECIMALS = 18;
    62
    63
        // Maximum number of secondary managers per strategy to prevent governance DoS on manager replacement
    64
        uint256 public constant MAX_SECONDARY_MANAGERS = 5;
    65
    66
        // Maximum number of strategies to process in `batchForwardPPS`
    67
        uint256 public constant MAX_STRATEGIES = 300;
    68
    69
        // Timelock for manager changes and Merkle root updates
    70
        uint256 private constant _MANAGER_CHANGE_TIMELOCK = 7 days;
    71
        uint256 private _hooksRootUpdateTimelock = 15 minutes;
    72
    73
        // Global hooks Merkle root data
    74
        bytes32 private _globalHooksRoot;
    75
        bytes32 private _proposedGlobalHooksRoot;
    76
        uint256 private _globalHooksRootEffectiveTime;
    77
        bool private _globalHooksRootVetoed;
    78
    79
        // Nonce for vault creation tracking
    80
        uint256 private _vaultCreationNonce;
    81
    82
        /*//////////////////////////////////////////////////////////////
    83
                                   MODIFIERS
    84
        //////////////////////////////////////////////////////////////*/
    85
        /// @notice Validates that msg.sender is the active PPS Oracle
    86
        modifier onlyPPSOracle() {
    87 60×
            if (!SUPER_GOVERNOR.isActivePPSOracle(msg.sender)) {
    88 0
                revert UNAUTHORIZED_PPS_ORACLE();
    89
            }
    90
            _;
    91
        }
    92
    93
        /// @notice Validates that a strategy exists (has been created by this aggregator)
    94
        modifier validStrategy(address strategy) {
    95 120×
            if (!_superVaultStrategies.contains(strategy))
    96 104×
                revert UNKNOWN_STRATEGY();
    97
            _;
    98
        }
    99
    100
        /*//////////////////////////////////////////////////////////////
    101
                                  CONSTRUCTOR
    102
        //////////////////////////////////////////////////////////////*/
    103
        /// @notice Initializes the SuperVaultAggregator
    104
        /// @param superGovernor_ Address of the SuperGovernor contract
    105
        /// @param vaultImpl_ Address of the pre-deployed SuperVault implementation
    106
        /// @param strategyImpl_ Address of the pre-deployed SuperVaultStrategy implementation
    107
        /// @param escrowImpl_ Address of the pre-deployed SuperVaultEscrow implementation
    108 0
        constructor(
    109
            address superGovernor_,
    110
            address vaultImpl_,
    111
            address strategyImpl_,
    112
            address escrowImpl_
    113
        ) {
    114
            if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
    115
            if (vaultImpl_ == address(0)) revert ZERO_ADDRESS();
    116
            if (strategyImpl_ == address(0)) revert ZERO_ADDRESS();
    117
            if (escrowImpl_ == address(0)) revert ZERO_ADDRESS();
    118
    119
            SUPER_GOVERNOR = ISuperGovernor(superGovernor_);
    120
            VAULT_IMPLEMENTATION = vaultImpl_;
    121
            STRATEGY_IMPLEMENTATION = strategyImpl_;
    122
            ESCROW_IMPLEMENTATION = escrowImpl_;
    123
        }
    124
    125
        /*//////////////////////////////////////////////////////////////
    126
                                VAULT CREATION
    127
        //////////////////////////////////////////////////////////////*/
    128
        /// @inheritdoc ISuperVaultAggregator
    129 22×
        function createVault(
    130
            VaultCreationParams calldata params
    131
        ) external returns (address superVault, address strategy, address escrow) {
    132
            // Input validation
    133
            if (
    134 18×
                params.asset == address(0) ||
    135 13×
                params.mainManager == address(0) ||
    136 13×
                params.feeConfig.recipient == address(0)
    137
            ) {
    138 13×
                revert ZERO_ADDRESS();
    139
            }
    140
    141
            // Initialize local variables struct to avoid stack too deep
    142
            VaultCreationLocalVars memory vars;
    143
    144
            // Increment nonce before creating proxies
    145 13×
            vars.currentNonce = _vaultCreationNonce++;
    146 12×
            vars.salt = keccak256(
    147 28×
                abi.encode(
    148
                    msg.sender,
    149
                    params.asset,
    150
                    params.name,
    151
                    params.symbol,
    152
                    vars.currentNonce
    153
                )
    154
            );
    155
    156
            // Create minimal proxies
    157 10×
            superVault = VAULT_IMPLEMENTATION.cloneDeterministic(vars.salt);
    158 17×
            escrow = ESCROW_IMPLEMENTATION.cloneDeterministic(vars.salt);
    159 16×
            strategy = STRATEGY_IMPLEMENTATION.cloneDeterministic(vars.salt);
    160
    161
            // Initialize superVault
    162 59×
            SuperVault(superVault).initialize(
    163
                params.asset,
    164
                params.name,
    165
                params.symbol,
    166
                strategy,
    167
                escrow
    168
            );
    169
    170
            // Initialize escrow
    171 38×
            SuperVaultEscrow(escrow).initialize(superVault, strategy);
    172
    173
            // Initialize strategy
    174 53×
            SuperVaultStrategy(payable(strategy)).initialize(
    175
                superVault,
    176
                params.feeConfig
    177
            );
    178
    179
            // Store vault trio in registry
    180 11×
            _superVaults.add(superVault);
    181
            _superVaultStrategies.add(strategy);
    182
            _superVaultEscrows.add(escrow);
    183
    184
            // Get asset decimals
    185 19×
            (bool success, uint8 assetDecimals) = params
    186
                .asset
    187
                .tryGetAssetDecimals();
    188
            if (!success) revert INVALID_ASSET();
    189 14×
            vars.initialPPS = 10 ** assetDecimals; // 1.0 as initial PPS
    190
    191
            // Validate maxStaleness against minimum required staleness
    192 67×
            if (params.maxStaleness < SUPER_GOVERNOR.getMinStaleness()) {
    193 13×
                revert MAX_STALENESS_TOO_LOW();
    194
            }
    195
    196
            // Initialize StrategyData individually to avoid mapping assignment issues
    197 20×
            _strategyData[strategy].pps = vars.initialPPS;
    198
            // Initialize standard deviation to 0
    199
            _strategyData[strategy].lastUpdateTimestamp = block.timestamp;
    200
            _strategyData[strategy].minUpdateInterval = params.minUpdateInterval;
    201
            _strategyData[strategy].maxStaleness = params.maxStaleness;
    202
            _strategyData[strategy].isPaused = false;
    203 42×
            _strategyData[strategy].mainManager = params.mainManager;
    204
    205 12×
            uint256 secondaryLen = params.secondaryManagers.length;
    206 13×
            for (uint256 i; i < secondaryLen; ++i) {
    207 19×
                _strategyData[strategy].secondaryManagers.add(
    208 30×
                    params.secondaryManagers[i]
    209
                );
    210
            }
    211
            if (
    212 20×
                _strategyData[strategy].secondaryManagers.length() >=
    213
                MAX_SECONDARY_MANAGERS
    214
            ) {
    215 13×
                revert TOO_MANY_SECONDARY_MANAGERS();
    216
            }
    217
    218
            // Set default threshold values
    219 22×
            _strategyData[strategy].dispersionThreshold = type(uint256).max; // Default: max (disabled)
    220
            _strategyData[strategy].deviationThreshold = type(uint256).max; // Default: max (disabled)
    221
    222 25×
            emit VaultDeployed(
    223
                superVault,
    224
                strategy,
    225
                escrow,
    226
                params.asset,
    227
                params.name,
    228 11×
                params.symbol,
    229
                vars.currentNonce
    230
            );
    231 14×
            emit PPSUpdated(
    232
                strategy,
    233
                vars.initialPPS,
    234
                0,
    235
                0,
    236
                0,
    237 15×
                _strategyData[strategy].lastUpdateTimestamp
    238
            );
    239
    240
            return (superVault, strategy, escrow);
    241
        }
    242
    243
        /*//////////////////////////////////////////////////////////////
    244
                              PPS UPDATE FUNCTIONS
    245
        //////////////////////////////////////////////////////////////*/
    246
        /// @inheritdoc ISuperVaultAggregator
    247 18×
        function forwardPPS(ForwardPPSArgs calldata args) external onlyPPSOracle {
    248
            uint256 strategiesLength = args.strategies.length;
    249
            if (strategiesLength > MAX_STRATEGIES) revert MAX_STRATEGIES_EXCEEDED();
    250
    251
            if (strategiesLength == 0) revert ZERO_ARRAY_LENGTH();
    252
            // Validate input array lengths
    253
            if (
    254 29×
                strategiesLength != args.ppss.length ||
    255 13×
                strategiesLength != args.ppsStdevs.length ||
    256 13×
                strategiesLength != args.validatorSets.length ||
    257 13×
                strategiesLength != args.timestamps.length ||
    258 13×
                strategiesLength != args.totalValidators.length
    259 13×
            ) revert ARRAY_LENGTH_MISMATCH();
    260
    261 61×
            bool paymentsEnabled = SUPER_GOVERNOR.isUpkeepPaymentsEnabled();
    262
            uint256 chargeableCount;
    263
            if (paymentsEnabled) {
    264 16×
                for (uint256 i; i < strategiesLength; ++i) {
    265
                    // Skip invalid strategies without reverting
    266 37×
                    if (!_superVaultStrategies.contains(args.strategies[i])) {
    267 39×
                        emit UnknownStrategy(args.strategies[i]);
    268
                        continue;
    269
                    }
    270
    271
                    // Skip when invalid timestamp is provided
    272 27×
                    if (args.timestamps[i] > block.timestamp) {
    273 16×
                        emit ProvidedTimestampExceedsBlockTimestamp(
    274 28×
                            args.strategies[i],
    275 21×
                            args.timestamps[i],
    276
                            block.timestamp
    277
                        );
    278
                        continue;
    279
                    }
    280
    281
                    // Skip Superform manager
    282 58×
                    address manager = _strategyData[args.strategies[i]].mainManager;
    283 62×
                    if (SUPER_GOVERNOR.isSuperformManager(manager)) {
    284 0
                        emit SuperformManager(args.strategies[i], manager);
    285 0
                        continue;
    286
                    }
    287
    288
                    // Skip if updateAuthority is in the authorized callers list
    289
                    // These are manager-designated keepers that should be exempt from fees
    290
                    // NOTE: Protected keepers cannot be added to this list (blocked in addAuthorizedCaller)
    291
                    /// @dev: cannot underflow; it's checked above already and it skips the entry if that's the case
    292
                    if (
    293 50×
                        _strategyData[args.strategies[i]]
    294
                            .authorizedCallers
    295 10×
                            .contains(args.updateAuthority)
    296
                    ) {
    297 12×
                        emit AuthorizedCaller(
    298 28×
                            args.strategies[i],
    299 10×
                            args.updateAuthority
    300
                        );
    301
                        continue;
    302
                    }
    303
    304
                    // Count only non-stale entries as chargeable
    305
                    if (
    306 29×
                        block.timestamp - args.timestamps[i] <=
    307 46×
                        _strategyData[args.strategies[i]].maxStaleness
    308
                    ) {
    309
                        ++chargeableCount;
    310
                    }
    311
                }
    312
            }
    313
    314
            ///@dev Total upkeep cost is determined by the oracle based on the number of chargeable entries
    315
            uint256 totalCost = paymentsEnabled
    316 35×
                ? SUPER_GOVERNOR.getUpkeepCostPerBatchUpdate(
    317
                    msg.sender,
    318
                    chargeableCount
    319
                )
    320
                : 0;
    321
    322
            // Compute per-entry charge
    323
            uint256 perEntry = 0;
    324 10×
            if (paymentsEnabled && chargeableCount > 0) {
    325 0
                perEntry = totalCost / chargeableCount;
    326
            }
    327
    328
            // Process all valid strategies
    329 16×
            for (uint256 i; i < strategiesLength; ++i) {
    330
                // Skip invalid strategies without reverting
    331 10×
                if (!_superVaultStrategies.contains(args.strategies[i])) continue;
    332
    333
                // Skip when invalid timestamp is provided (future timestamp)
    334 27×
                if (args.timestamps[i] > block.timestamp) {
    335 16×
                    emit ProvidedTimestampExceedsBlockTimestamp(
    336 28×
                        args.strategies[i],
    337 21×
                        args.timestamps[i],
    338
                        block.timestamp
    339
                    );
    340
                    continue;
    341
                }
    342
    343
                uint256 upkeepCost;
    344
                if (paymentsEnabled) {
    345
                    // check exemption due to staleness of a given strategy
    346
                    /// @dev cannot underflow as it's already checked above, in the previous `for` loop
    347
                    if (
    348 0
                        block.timestamp - args.timestamps[i] >
    349 0
                        _strategyData[args.strategies[i]].maxStaleness
    350
                    ) {
    351 0
                        upkeepCost = 0;
    352 0
                        emit StaleUpdate(
    353 0
                            args.strategies[i],
    354 0
                            args.updateAuthority,
    355 0
                            args.timestamps[i]
    356
                        );
    357 0
                    } else {
    358 0
                        address manager = _strategyData[args.strategies[i]]
    359
                            .mainManager;
    360 0
                        if (
    361 0
                            SUPER_GOVERNOR.isSuperformManager(manager) ||
    362 0
                            _strategyData[args.strategies[i]]
    363
                                .authorizedCallers
    364 0
                                .contains(args.updateAuthority)
    365
                        ) {
    366 0
                            upkeepCost = 0;
    367
                        } else {
    368
                            // Split the total batch cost fairly across chargeable entries
    369 0
                            upkeepCost = perEntry;
    370
                        }
    371
                    }
    372
                }
    373
    374
                // Forward update
    375
                _forwardPPS(
    376 44×
                    PPSUpdateData({
    377 28×
                        strategy: args.strategies[i],
    378
                        isExempt: (!paymentsEnabled) || (upkeepCost == 0), // If payments are disabled or the update is exempt from UP payments
    379 24×
                        pps: args.ppss[i],
    380 24×
                        ppsStdev: args.ppsStdevs[i],
    381 24×
                        validatorSet: args.validatorSets[i],
    382 24×
                        totalValidators: args.totalValidators[i],
    383 24×
                        timestamp: args.timestamps[i],
    384
                        upkeepCost: upkeepCost
    385
                    })
    386
                );
    387
            }
    388
        }
    389
    390
        /*//////////////////////////////////////////////////////////////
    391
                            UPKEEP MANAGEMENT
    392
        //////////////////////////////////////////////////////////////*/
    393
        /// @inheritdoc ISuperVaultAggregator
    394 11×
        function depositUpkeep(address manager, uint256 amount) external {
    395 19×
            if (amount == 0) revert ZERO_AMOUNT();
    396
    397
            // Get the UP token address from SUPER_GOVERNOR
    398 121×
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    399
    400
            // Transfer UP tokens from msg.sender to this contract
    401
            IERC20(upToken).safeTransferFrom(msg.sender, address(this), amount);
    402
    403
            // Update upkeep balance
    404 27×
            _managerUpkeepBalance[manager] += amount;
    405
    406
            emit UpkeepDeposited(manager, amount);
    407
        }
    408
    409
        /// @inheritdoc ISuperVaultAggregator
    410 22×
        function claimUpkeep(uint256 amount) external {
    411
            // Only SUPER_GOVERNOR can claim upkeep
    412 13×
            if (msg.sender != address(SUPER_GOVERNOR)) {
    413 13×
                revert CALLER_NOT_AUTHORIZED();
    414
            }
    415
    416 21×
            if (claimableUpkeep < amount) revert INSUFFICIENT_UPKEEP();
    417 19×
            claimableUpkeep -= amount;
    418
    419
            // Get the UP token address from SUPER_GOVERNOR
    420 122×
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    421
    422
            // Transfer UP tokens to `SuperBank`
    423
            address _superBank = _getSuperBank();
    424
            IERC20(upToken).safeTransfer(_superBank, amount);
    425
            emit UpkeepClaimed(_superBank, amount);
    426
        }
    427
    428
        /// @inheritdoc ISuperVaultAggregator
    429 11×
        function withdrawUpkeep(uint256 amount) external {
    430 19×
            if (amount == 0) revert ZERO_AMOUNT();
    431
    432
            // Check sufficient balance
    433 17×
            if (_managerUpkeepBalance[msg.sender] < amount) {
    434 13×
                revert INSUFFICIENT_UPKEEP_BALANCE();
    435
            }
    436
    437
            // Get the UP token address from SUPER_GOVERNOR
    438 122×
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    439
    440
            // Update upkeep balance
    441
            unchecked {
    442 19×
                _managerUpkeepBalance[msg.sender] -= amount;
    443
            }
    444
    445
            // Transfer UP tokens to manager
    446
            IERC20(upToken).safeTransfer(msg.sender, amount);
    447
    448
            emit UpkeepWithdrawn(msg.sender, amount);
    449
        }
    450
    451
        /*//////////////////////////////////////////////////////////////
    452
                            STAKE MANAGEMENT
    453
        //////////////////////////////////////////////////////////////*/
    454
        /// @notice Deposits UP tokens as stake for manager economic security
    455
        /// @param manager Address of the manager to deposit stake for
    456
        /// @param amount Amount of UP tokens to deposit as stake
    457 19×
        function depositStake(address manager, uint256 amount) external {
    458 19×
            if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency
    459 18×
            if (manager == address(0)) revert ZERO_ADDRESS();
    460
    461
            // Get the UP token address from SUPER_GOVERNOR
    462 121×
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    463
    464
            // Transfer UP tokens from msg.sender to this contract
    465
            IERC20(upToken).safeTransferFrom(msg.sender, address(this), amount);
    466
    467
            // Update stake balance
    468 27×
            _managerStakeBalance[manager] += amount;
    469
    470 23×
            emit StakeDeposited(manager, amount);
    471
        }
    472
    473
        /// @notice Withdraws UP tokens from stake balance
    474
        /// @param amount Amount of UP tokens to withdraw from stake
    475 11×
        function withdrawStake(uint256 amount) external {
    476 19×
            if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency
    477
    478
            // Check sufficient balance
    479 17×
            if (_managerStakeBalance[msg.sender] < amount) {
    480 13×
                revert INSUFFICIENT_STAKE_BALANCE();
    481
            }
    482
    483
            // Get the UP token address from SUPER_GOVERNOR
    484 122×
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    485
    486
            // Update stake balance
    487
            unchecked {
    488 19×
                _managerStakeBalance[msg.sender] -= amount;
    489
            }
    490
    491
            // Transfer UP tokens to manager
    492
            IERC20(upToken).safeTransfer(msg.sender, amount);
    493
    494
            emit StakeWithdrawn(msg.sender, amount);
    495
        }
    496
    497
        /// @notice Slashes a manager's stake balance by a specified amount
    498
        /// @param manager The manager whose stake will be slashed
    499
        /// @param amount The amount of UP tokens to slash from the manager's stake balance
    500 11×
        function slashStake(address manager, uint256 amount) external {
    501
            // Only SUPER_GOVERNOR can slash stake
    502
            if (msg.sender != address(SUPER_GOVERNOR)) {
    503 13×
                revert CALLER_NOT_AUTHORIZED();
    504
            }
    505
    506
            // Validate inputs
    507 0
            if (manager == address(0)) revert ZERO_ADDRESS();
    508 0
            if (amount == 0) revert ZERO_ADDRESS(); // Reusing error code for consistency
    509
    510
            // Check if manager has sufficient stake balance to slash
    511 0
            if (_managerStakeBalance[manager] < amount) {
    512 0
                revert INSUFFICIENT_STAKE_BALANCE();
    513
            }
    514
    515
            // Reduce manager's stake balance
    516 0
            _managerStakeBalance[manager] -= amount;
    517
    518
            // Get the UP token address and SuperBank address
    519 0
            address upToken = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.UP());
    520 0
            address superBank = _getSuperBank();
    521
    522
            // Transfer slashed amount directly to SuperBank
    523 0
            IERC20(upToken).safeTransfer(superBank, amount);
    524
    525
            // Emit event for transparency
    526 0
            emit StakeSlashed(manager, amount);
    527
        }
    528
    529
        /*//////////////////////////////////////////////////////////////
    530
                            AUTHORIZED CALLER MANAGEMENT
    531
        //////////////////////////////////////////////////////////////*/
    532
        /// @inheritdoc ISuperVaultAggregator
    533 15×
        function addAuthorizedCaller(
    534
            address strategy,
    535
            address caller
    536
        ) external validStrategy(strategy) {
    537
            // Either primary or secondary manager can add authorized callers
    538
            if (!isAnyManager(msg.sender, strategy))
    539 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    540
    541 18×
            if (caller == address(0)) revert ZERO_ADDRESS();
    542
    543
            // Prevent managers from adding protected keepers to circumvent fees
    544 60×
            if (SUPER_GOVERNOR.isProtectedKeeper(caller)) {
    545 0
                revert CANNOT_ADD_PROTECTED_KEEPER();
    546
            }
    547
    548
            // Check if caller is already authorized and add if not
    549 23×
            if (!_strategyData[strategy].authorizedCallers.add(caller)) {
    550 13×
                revert CALLER_ALREADY_AUTHORIZED();
    551
            }
    552 14×
            emit AuthorizedCallerAdded(strategy, caller);
    553
        }
    554
    555
        /// @inheritdoc ISuperVaultAggregator
    556 15×
        function removeAuthorizedCaller(
    557
            address strategy,
    558
            address caller
    559
        ) external validStrategy(strategy) {
    560
            // Either primary or secondary manager can remove authorized callers
    561
            if (!isAnyManager(msg.sender, strategy))
    562 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    563
    564
            // Remove the caller
    565 23×
            if (!_strategyData[strategy].authorizedCallers.remove(caller)) {
    566 13×
                revert CALLER_NOT_AUTHORIZED();
    567
            }
    568 14×
            emit AuthorizedCallerRemoved(strategy, caller);
    569
        }
    570
    571
        /*//////////////////////////////////////////////////////////////
    572
                           MANAGER MANAGEMENT FUNCTIONS
    573
        //////////////////////////////////////////////////////////////*/
    574
        /// @inheritdoc ISuperVaultAggregator
    575 15×
        function addSecondaryManager(
    576
            address strategy,
    577
            address manager
    578
        ) external validStrategy(strategy) {
    579
            // Only the primary manager can add secondary managers
    580 25×
            if (msg.sender != _strategyData[strategy].mainManager)
    581 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    582
    583 18×
            if (manager == address(0)) revert ZERO_ADDRESS();
    584
    585
            // Check if manager is already the primary manager
    586 30×
            if (_strategyData[strategy].mainManager == manager)
    587 13×
                revert MANAGER_ALREADY_EXISTS();
    588
    589
            // Enforce a cap on secondary managers to prevent governance DoS on changePrimaryManager
    590
            if (
    591 20×
                _strategyData[strategy].secondaryManagers.length() >=
    592
                MAX_SECONDARY_MANAGERS
    593
            ) {
    594 0
                revert TOO_MANY_SECONDARY_MANAGERS();
    595
            }
    596
    597
            // Add as secondary manager using EnumerableSet
    598 23×
            if (!_strategyData[strategy].secondaryManagers.add(manager))
    599 13×
                revert MANAGER_ALREADY_EXISTS();
    600
    601 14×
            emit SecondaryManagerAdded(strategy, manager);
    602
        }
    603
    604
        /// @inheritdoc ISuperVaultAggregator
    605 15×
        function removeSecondaryManager(
    606
            address strategy,
    607
            address manager
    608
        ) external validStrategy(strategy) {
    609
            // Only the primary manager can remove secondary managers
    610 25×
            if (msg.sender != _strategyData[strategy].mainManager)
    611 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    612
    613
            // Remove the manager using EnumerableSet
    614 23×
            if (!_strategyData[strategy].secondaryManagers.remove(manager))
    615 13×
                revert MANAGER_NOT_FOUND();
    616
    617 14×
            emit SecondaryManagerRemoved(strategy, manager);
    618
        }
    619
    620
        /// @inheritdoc ISuperVaultAggregator
    621 17×
        function updatePPSVerificationThresholds(
    622
            address strategy,
    623
            uint256 dispersionThreshold_,
    624
            uint256 deviationThreshold_,
    625
            uint256 mnThreshold_
    626
        ) external validStrategy(strategy) {
    627
            // Since this is a risky call, we only allow main managers as callers
    628 25×
            if (msg.sender != _strategyData[strategy].mainManager) {
    629 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    630
            }
    631
    632
            // Update the thresholds
    633 22×
            _strategyData[strategy].dispersionThreshold = dispersionThreshold_;
    634
            _strategyData[strategy].deviationThreshold = deviationThreshold_;
    635
            _strategyData[strategy].mnThreshold = mnThreshold_;
    636
    637
            // Emit the event
    638 12×
            emit PPSVerificationThresholdsUpdated(
    639
                strategy,
    640
                dispersionThreshold_,
    641
                deviationThreshold_,
    642
                mnThreshold_
    643
            );
    644
        }
    645
    646
        /// @inheritdoc ISuperVaultAggregator
    647 0
        function changeGlobalLeavesStatus(
    648
            bytes32[] memory leaves,
    649
            bool[] memory statuses,
    650
            address strategy
    651 0
        ) external validStrategy(strategy) {
    652
            // Only the primary manager can change global leaves status
    653 0
            if (msg.sender != _strategyData[strategy].mainManager) {
    654 0
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    655
            }
    656 0
            uint256 leavesLen = leaves.length;
    657
            // Check array lengths match
    658 0
            if (leavesLen != statuses.length) {
    659 0
                revert MISMATCHED_ARRAY_LENGTHS();
    660
            }
    661
    662
            // Update banned status for each leaf
    663 0
            for (uint256 i; i < leavesLen; i++) {
    664 0
                _strategyData[strategy].bannedLeaves[leaves[i]] = statuses[i];
    665
            }
    666
    667
            // Emit event
    668 0
            emit GlobalLeavesStatusChanged(strategy, leaves, statuses);
    669
        }
    670
    671
        /// @inheritdoc ISuperVaultAggregator
    672 26×
        function changePrimaryManager(
    673
            address strategy,
    674
            address newManager
    675
        ) external validStrategy(strategy) {
    676
            // Only SuperGovernor can call this
    677 13×
            if (msg.sender != address(SUPER_GOVERNOR)) {
    678 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    679
            }
    680
    681
            if (strategy == address(0)) revert ZERO_ADDRESS();
    682
    683
            if (newManager == address(0)) revert ZERO_ADDRESS();
    684
    685 24×
            address oldManager = _strategyData[strategy].mainManager;
    686
    687
            // SECURITY: Clear any pending manager proposals to prevent malicious re-takeover
    688
            _strategyData[strategy].proposedManager = address(0);
    689
            _strategyData[strategy].managerChangeEffectiveTime = 0;
    690
    691
            // SECURITY: Clear any pending hooks root proposals to prevent malicious hook updates
    692
            _strategyData[strategy].proposedHooksRoot = bytes32(0);
    693
            _strategyData[strategy].hooksRootEffectiveTime = 0;
    694
    695
            // SECURITY: Clear all secondary managers as they may be controlled by malicious manager
    696
            // Get all secondary managers first to emit proper events
    697
            address[] memory clearedSecondaryManagers = _strategyData[strategy]
    698
                .secondaryManagers
    699
                .values();
    700
    701
            // Clear the entire secondary managers set
    702 14×
            for (uint256 i = 0; i < clearedSecondaryManagers.length; i++) {
    703 28×
                _strategyData[strategy].secondaryManagers.remove(
    704 15×
                    clearedSecondaryManagers[i]
    705
                );
    706 28×
                emit SecondaryManagerRemoved(strategy, clearedSecondaryManagers[i]);
    707
            }
    708
    709
            // Set the new primary manager
    710 36×
            _strategyData[strategy].mainManager = newManager;
    711
    712
            emit PrimaryManagerChanged(strategy, oldManager, newManager);
    713
        }
    714
    715
        /// @inheritdoc ISuperVaultAggregator
    716 15×
        function proposeChangePrimaryManager(
    717
            address strategy,
    718
            address newManager
    719
        ) external validStrategy(strategy) {
    720
            // Only secondary managers can propose changes to the primary manager
    721 23×
            if (!_strategyData[strategy].secondaryManagers.contains(msg.sender)) {
    722 13×
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    723
            }
    724
    725
            if (newManager == address(0)) revert ZERO_ADDRESS();
    726
    727
            // Set up the proposal with 7-day timelock
    728
            uint256 effectiveTime = block.timestamp + _MANAGER_CHANGE_TIMELOCK;
    729
    730
            // Store proposal in the strategy data
    731 33×
            _strategyData[strategy].proposedManager = newManager;
    732
            _strategyData[strategy].managerChangeEffectiveTime = effectiveTime;
    733
    734 11×
            emit PrimaryManagerChangeProposed(
    735
                strategy,
    736
                msg.sender,
    737
                newManager,
    738
                effectiveTime
    739
            );
    740
        }
    741
    742
        /// @inheritdoc ISuperVaultAggregator
    743 14×
        function executeChangePrimaryManager(
    744
            address strategy
    745
        ) external validStrategy(strategy) {
    746
            // Check if there is a pending proposal
    747 20×
            if (_strategyData[strategy].proposedManager == address(0))
    748 13×
                revert NO_PENDING_MANAGER_CHANGE();
    749
    750
            // Check if the timelock period has passed
    751
            if (
    752 17×
                block.timestamp < _strategyData[strategy].managerChangeEffectiveTime
    753 13×
            ) revert TIMELOCK_NOT_EXPIRED();
    754
    755 23×
            address newManager = _strategyData[strategy].proposedManager;
    756
            address oldManager = _strategyData[strategy].mainManager;
    757
    758
            // If new manager is already a secondary manager, remove them
    759
            _strategyData[strategy].secondaryManagers.remove(newManager);
    760
    761
            // Make the old primary manager a secondary manager
    762
            if (
    763 20×
                _strategyData[strategy].secondaryManagers.length() <
    764
                MAX_SECONDARY_MANAGERS
    765
            ) {
    766 21×
                _strategyData[strategy].secondaryManagers.add(oldManager);
    767
            }
    768
    769
            // Set the new primary manager
    770 37×
            _strategyData[strategy].mainManager = newManager;
    771
    772
            // Clear the proposal
    773
            _strategyData[strategy].proposedManager = address(0);
    774
    775
            emit PrimaryManagerChanged(strategy, oldManager, newManager);
    776
        }
    777
    778
        /*//////////////////////////////////////////////////////////////
    779
                            HOOK VALIDATION FUNCTIONS
    780
        //////////////////////////////////////////////////////////////*/
    781
        /// @inheritdoc ISuperVaultAggregator
    782 0
        function setHooksRootUpdateTimelock(uint256 newTimelock) external {
    783
            // Only SUPER_GOVERNOR can update the timelock
    784 0
            if (msg.sender != address(SUPER_GOVERNOR)) {
    785 0
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    786
            }
    787
    788
            // Update the timelock
    789 0
            _hooksRootUpdateTimelock = newTimelock;
    790
    791 0
            emit HooksRootUpdateTimelockChanged(newTimelock);
    792
        }
    793
    794
        /// @inheritdoc ISuperVaultAggregator
    795 11×
        function proposeGlobalHooksRoot(bytes32 newRoot) external {
    796
            // Only SUPER_GOVERNOR can update the global hooks root
    797
            if (msg.sender != address(SUPER_GOVERNOR)) {
    798 0
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    799
            }
    800
    801
            // Set new root with timelock
    802
            _proposedGlobalHooksRoot = newRoot;
    803 12×
            uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock;
    804
            _globalHooksRootEffectiveTime = effectiveTime;
    805
    806
            emit GlobalHooksRootUpdateProposed(newRoot, effectiveTime);
    807
        }
    808
    809
        /// @inheritdoc ISuperVaultAggregator
    810 10×
        function executeGlobalHooksRootUpdate() external {
    811 0
            bytes32 proposedRoot = _proposedGlobalHooksRoot;
    812
            // Ensure there is a pending proposal
    813 0
            if (proposedRoot == bytes32(0)) {
    814 0
                revert NO_PENDING_GLOBAL_ROOT_CHANGE();
    815
            }
    816
    817
            // Check if timelock period has elapsed
    818 0
            if (block.timestamp < _globalHooksRootEffectiveTime) {
    819 0
                revert ROOT_UPDATE_NOT_READY();
    820
            }
    821
    822
            // Update the global hooks root
    823 0
            bytes32 oldRoot = _globalHooksRoot;
    824 0
            _globalHooksRoot = _proposedGlobalHooksRoot;
    825 0
            _globalHooksRootEffectiveTime = 0;
    826 0
            _proposedGlobalHooksRoot = bytes32(0);
    827
    828 16×
            emit GlobalHooksRootUpdated(oldRoot, proposedRoot);
    829
        }
    830
    831
        /// @inheritdoc ISuperVaultAggregator
    832 0
        function setGlobalHooksRootVetoStatus(bool vetoed) external {
    833
            // Only SuperGovernor can call this
    834 0
            if (msg.sender != address(SUPER_GOVERNOR)) {
    835 0
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    836
            }
    837
    838
            // Don't emit event if status doesn't change
    839 0
            if (_globalHooksRootVetoed == vetoed) {
    840
                return;
    841
            }
    842
    843
            // Update veto status
    844 0
            _globalHooksRootVetoed = vetoed;
    845
    846 0
            emit GlobalHooksRootVetoStatusChanged(vetoed, _globalHooksRoot);
    847
        }
    848
    849
        /// @inheritdoc ISuperVaultAggregator
    850 0
        function proposeStrategyHooksRoot(
    851
            address strategy,
    852
            bytes32 newRoot
    853 0
        ) external validStrategy(strategy) {
    854
            // Only the main manager can propose strategy-specific hooks root
    855 0
            if (_strategyData[strategy].mainManager != msg.sender) {
    856 0
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    857
            }
    858
    859
            // Set proposed root with timelock
    860 0
            _strategyData[strategy].proposedHooksRoot = newRoot;
    861 0
            uint256 effectiveTime = block.timestamp + _hooksRootUpdateTimelock;
    862 0
            _strategyData[strategy].hooksRootEffectiveTime = effectiveTime;
    863
    864 0
            emit StrategyHooksRootUpdateProposed(
    865
                strategy,
    866 0
                msg.sender,
    867
                newRoot,
    868
                effectiveTime
    869
            );
    870
        }
    871
    872
        /// @inheritdoc ISuperVaultAggregator
    873 0
        function executeStrategyHooksRootUpdate(
    874
            address strategy
    875 0
        ) external validStrategy(strategy) {
    876 0
            bytes32 proposedRoot = _strategyData[strategy].proposedHooksRoot;
    877
            // Ensure there is a pending proposal
    878 0
            if (proposedRoot == bytes32(0)) {
    879 0
                revert NO_PENDING_MANAGER_CHANGE(); // Reusing error for simplicity
    880
            }
    881
    882
            // Check if timelock period has elapsed
    883 0
            if (block.timestamp < _strategyData[strategy].hooksRootEffectiveTime) {
    884 0
                revert ROOT_UPDATE_NOT_READY();
    885
            }
    886
    887
            // Update the strategy's hooks root
    888 0
            bytes32 oldRoot = _strategyData[strategy].managerHooksRoot;
    889 0
            _strategyData[strategy].managerHooksRoot = proposedRoot;
    890
    891
            // Reset proposal state
    892 0
            _strategyData[strategy].proposedHooksRoot = bytes32(0);
    893 0
            _strategyData[strategy].hooksRootEffectiveTime = 0;
    894
    895 0
            emit StrategyHooksRootUpdated(strategy, oldRoot, proposedRoot);
    896
        }
    897
    898
        /// @inheritdoc ISuperVaultAggregator
    899
        function setStrategyHooksRootVetoStatus(
    900
            address strategy,
    901
            bool vetoed
    902 0
        ) external validStrategy(strategy) {
    903
            // Only SuperGovernor can call this
    904 0
            if (msg.sender != address(SUPER_GOVERNOR)) {
    905 0
                revert UNAUTHORIZED_UPDATE_AUTHORITY();
    906
            }
    907
    908
            // Don't emit event if status doesn't change
    909 0
            if (_strategyData[strategy].hooksRootVetoed == vetoed) {
    910 0
                return;
    911
            }
    912
    913
            // Update veto status
    914 0
            _strategyData[strategy].hooksRootVetoed = vetoed;
    915
    916 0
            emit StrategyHooksRootVetoStatusChanged(
    917
                strategy,
    918
                vetoed,
    919 0
                _strategyData[strategy].managerHooksRoot
    920
            );
    921
        }
    922
        /// @inheritdoc ISuperVaultAggregator
    923
    924
        function isGlobalHooksRootVetoed() external view returns (bool vetoed) {
    925
            return _globalHooksRootVetoed;
    926
        }
    927
    928
        /// @inheritdoc ISuperVaultAggregator
    929 0
        function isStrategyHooksRootVetoed(
    930
            address strategy
    931 0
        ) external view returns (bool vetoed) {
    932 0
            return _strategyData[strategy].hooksRootVetoed;
    933
        }
    934
    935
        /*//////////////////////////////////////////////////////////////
    936
                                  VIEW FUNCTIONS
    937
        //////////////////////////////////////////////////////////////*/
    938
    939
        /// @inheritdoc ISuperVaultAggregator
    940 0
        function getCurrentNonce() external view returns (uint256) {
    941 0
            return _vaultCreationNonce;
    942
        }
    943
    944
        /// @inheritdoc ISuperVaultAggregator
    945 0
        function getHooksRootUpdateTimelock() external view returns (uint256) {
    946 0
            return _hooksRootUpdateTimelock;
    947
        }
    948
    949
        /// @inheritdoc ISuperVaultAggregator
    950 72×
        function getPPS(
    951
            address strategy
    952 18×
        ) external view validStrategy(strategy) returns (uint256 pps) {
    953 78×
            return _strategyData[strategy].pps;
    954
        }
    955
    956
        /// @inheritdoc ISuperVaultAggregator
    957 0
        function getPPSWithStdDev(
    958
            address strategy
    959
        )
    960
            external
    961
            view
    962 0
            validStrategy(strategy)
    963 0
            returns (uint256 pps, uint256 ppsStdev)
    964
        {
    965 0
            return (_strategyData[strategy].pps, _strategyData[strategy].ppsStdev);
    966
        }
    967
    968
        /// @inheritdoc ISuperVaultAggregator
    969 0
        function getLastUpdateTimestamp(
    970
            address strategy
    971 0
        ) external view returns (uint256 timestamp) {
    972 0
            return _strategyData[strategy].lastUpdateTimestamp;
    973
        }
    974
    975
        /// @inheritdoc ISuperVaultAggregator
    976 0
        function getMinUpdateInterval(
    977
            address strategy
    978 0
        ) external view returns (uint256 interval) {
    979 0
            return _strategyData[strategy].minUpdateInterval;
    980
        }
    981
    982
        /// @inheritdoc ISuperVaultAggregator
    983 0
        function getMaxStaleness(
    984
            address strategy
    985 0
        ) external view returns (uint256 staleness) {
    986 0
            return _strategyData[strategy].maxStaleness;
    987
        }
    988
    989
        /// @inheritdoc ISuperVaultAggregator
    990 0
        function getPPSVerificationThresholds(
    991
            address strategy
    992
        )
    993
            external
    994
            view
    995 0
            validStrategy(strategy)
    996
            returns (
    997 0
                uint256 dispersionThreshold,
    998
                uint256 deviationThreshold,
    999
                uint256 mnThreshold
    1000
            )
    1001
        {
    1002
            return (
    1003 0
                _strategyData[strategy].dispersionThreshold,
    1004 0
                _strategyData[strategy].deviationThreshold,
    1005 0
                _strategyData[strategy].mnThreshold
    1006
            );
    1007
        }
    1008
    1009
        /// @inheritdoc ISuperVaultAggregator
    1010 54×
        function isStrategyPaused(
    1011
            address strategy
    1012
        ) external view returns (bool isPaused) {
    1013 96×
            return _strategyData[strategy].isPaused;
    1014
        }
    1015
    1016
        /// @inheritdoc ISuperVaultAggregator
    1017 0
        function getUpkeepBalance(
    1018
            address manager
    1019 0
        ) external view returns (uint256 balance) {
    1020 0
            return _managerUpkeepBalance[manager];
    1021
        }
    1022
    1023
        /// @notice Gets the current stake balance for a manager
    1024
        /// @param manager Address of the manager
    1025
        /// @return balance Current stake balance in UP tokens
    1026 0
        function getStakeBalance(
    1027
            address manager
    1028 0
        ) external view returns (uint256 balance) {
    1029 0
            return _managerStakeBalance[manager];
    1030
        }
    1031
    1032
        /// @inheritdoc ISuperVaultAggregator
    1033 0
        function getAuthorizedCallers(
    1034
            address strategy
    1035 0
        ) external view returns (address[] memory callers) {
    1036 0
            return _strategyData[strategy].authorizedCallers.values();
    1037
        }
    1038
    1039
        /// @inheritdoc ISuperVaultAggregator
    1040 0
        function getMainManager(
    1041
            address strategy
    1042 0
        ) external view returns (address manager) {
    1043 0
            manager = _strategyData[strategy].mainManager;
    1044 0
            if (manager == address(0)) revert ZERO_ADDRESS();
    1045
    1046
            return manager;
    1047
        }
    1048
    1049
        /// @inheritdoc ISuperVaultAggregator
    1050
        function isMainManager(
    1051
            address manager,
    1052
            address strategy
    1053
        ) external view returns (bool) {
    1054 24×
            return _strategyData[strategy].mainManager == manager;
    1055
        }
    1056
    1057
        /// @inheritdoc ISuperVaultAggregator
    1058 0
        function getSecondaryManagers(
    1059
            address strategy
    1060 0
        ) external view returns (address[] memory) {
    1061 0
            return _strategyData[strategy].secondaryManagers.values();
    1062
        }
    1063
    1064
        /// @inheritdoc ISuperVaultAggregator
    1065 0
        function isSecondaryManager(
    1066
            address manager,
    1067
            address strategy
    1068 0
        ) external view returns (bool) {
    1069 0
            return _strategyData[strategy].secondaryManagers.contains(manager);
    1070
        }
    1071
    1072
        /// @inheritdoc ISuperVaultAggregator
    1073 23×
        function isAnyManager(
    1074
            address manager,
    1075
            address strategy
    1076
        ) public view returns (bool) {
    1077
            // Check if primary manager
    1078 81×
            if (_strategyData[strategy].mainManager == manager) {
    1079
                return true;
    1080
            }
    1081
    1082
            // Check if secondary manager using EnumerableSet
    1083 57×
            return _strategyData[strategy].secondaryManagers.contains(manager);
    1084
        }
    1085
    1086
        /// @inheritdoc ISuperVaultAggregator
    1087 0
        function getAllSuperVaults() external view returns (address[] memory) {
    1088 0
            return _superVaults.values();
    1089
        }
    1090
    1091
        /// @inheritdoc ISuperVaultAggregator
    1092
        function superVaults(uint256 index) external view returns (address) {
    1093 0
            if (index >= _superVaults.length()) revert INDEX_OUT_OF_BOUNDS();
    1094
            return _superVaults.at(index);
    1095
        }
    1096
    1097
        /// @inheritdoc ISuperVaultAggregator
    1098 0
        function getAllSuperVaultStrategies()
    1099
            external
    1100
            view
    1101 0
            returns (address[] memory)
    1102
        {
    1103 0
            return _superVaultStrategies.values();
    1104
        }
    1105
    1106
        /// @inheritdoc ISuperVaultAggregator
    1107 0
        function superVaultStrategies(
    1108
            uint256 index
    1109 0
        ) external view returns (address) {
    1110 0
            if (index >= _superVaultStrategies.length())
    1111 0
                revert INDEX_OUT_OF_BOUNDS();
    1112 0
            return _superVaultStrategies.at(index);
    1113
        }
    1114
    1115
        /// @inheritdoc ISuperVaultAggregator
    1116
        function getAllSuperVaultEscrows()
    1117
            external
    1118
            view
    1119 0
            returns (address[] memory)
    1120
        {
    1121
            return _superVaultEscrows.values();
    1122
        }
    1123
    1124
        /// @inheritdoc ISuperVaultAggregator
    1125 0
        function superVaultEscrows(uint256 index) external view returns (address) {
    1126 0
            if (index >= _superVaultEscrows.length()) revert INDEX_OUT_OF_BOUNDS();
    1127 0
            return _superVaultEscrows.at(index);
    1128
        }
    1129
    1130
        /// @inheritdoc ISuperVaultAggregator
    1131 0
        function validateHook(
    1132
            address strategy,
    1133
            ValidateHookArgs calldata args
    1134 0
        ) external view virtual returns (bool isValid) {
    1135
            // Cache all state variables in struct
    1136 0
            HookValidationCache memory cache = HookValidationCache({
    1137 0
                globalHooksRootVetoed: _globalHooksRootVetoed,
    1138 0
                globalHooksRoot: _globalHooksRoot,
    1139 0
                strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed,
    1140 0
                strategyRoot: _strategyData[strategy].managerHooksRoot
    1141
            });
    1142
    1143
            // Early return false if either global or strategy hooks root is vetoed
    1144 0
            if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) {
    1145 0
                return false;
    1146
            }
    1147
    1148
            // Try to validate against global root first
    1149 0
            if (
    1150 0
                _validateSingleHook(
    1151 0
                    args.hookAddress,
    1152 0
                    args.hookArgs,
    1153 0
                    args.globalProof,
    1154 0
                    true,
    1155 0
                    cache,
    1156 0
                    strategy
    1157
                )
    1158
            ) {
    1159 0
                return true;
    1160
            }
    1161
    1162
            // If global validation fails, try strategy root
    1163 0
            return
    1164 0
                _validateSingleHook(
    1165 0
                    args.hookAddress,
    1166 0
                    args.hookArgs,
    1167 0
                    args.strategyProof,
    1168 0
                    false,
    1169 0
                    cache,
    1170 0
                    strategy
    1171
                );
    1172
        }
    1173
    1174
        /// @inheritdoc ISuperVaultAggregator
    1175 42×
        function validateHooks(
    1176
            address strategy,
    1177
            ValidateHookArgs[] calldata argsArray
    1178 0
        ) external view returns (bool[] memory validHooks) {
    1179 0
            uint256 length = argsArray.length;
    1180
    1181
            // Cache all state variables in struct
    1182 0
            HookValidationCache memory cache = HookValidationCache({
    1183 0
                globalHooksRootVetoed: _globalHooksRootVetoed,
    1184 0
                globalHooksRoot: _globalHooksRoot,
    1185 0
                strategyHooksRootVetoed: _strategyData[strategy].hooksRootVetoed,
    1186 0
                strategyRoot: _strategyData[strategy].managerHooksRoot
    1187
            });
    1188
    1189
            // Early return all false if either global or strategy hooks root is vetoed
    1190 0
            if (cache.globalHooksRootVetoed || cache.strategyHooksRootVetoed) {
    1191 0
                return new bool[](length); // Array initialized with all false values
    1192
            }
    1193
    1194
            // Validate each hook
    1195 0
            validHooks = new bool[](length);
    1196 0
            for (uint256 i; i < length; i++) {
    1197
                // Try global root first
    1198 0
                if (
    1199 0
                    _validateSingleHook(
    1200 0
                        argsArray[i].hookAddress,
    1201 0
                        argsArray[i].hookArgs,
    1202 0
                        argsArray[i].globalProof,
    1203 0
                        true,
    1204 0
                        cache,
    1205 0
                        strategy
    1206
                    )
    1207
                ) {
    1208 0
                    validHooks[i] = true;
    1209
                } else {
    1210
                    // Try strategy root
    1211 0
                    validHooks[i] = _validateSingleHook(
    1212 0
                        argsArray[i].hookAddress,
    1213 0
                        argsArray[i].hookArgs,
    1214 0
                        argsArray[i].strategyProof,
    1215 0
                        false,
    1216 0
                        cache,
    1217 0
                        strategy
    1218
                    );
    1219
                }
    1220
                // If both conditions fail, validHooks[i] remains false (default value)
    1221
            }
    1222
    1223 0
            return validHooks;
    1224
        }
    1225
    1226
        /// @inheritdoc ISuperVaultAggregator
    1227 0
        function getGlobalHooksRoot() external view returns (bytes32 root) {
    1228 0
            return _globalHooksRoot;
    1229
        }
    1230
    1231
        /// @inheritdoc ISuperVaultAggregator
    1232 0
        function getProposedGlobalHooksRoot()
    1233
            external
    1234
            view
    1235
            returns (bytes32 root, uint256 effectiveTime)
    1236
        {
    1237 0
            return (_proposedGlobalHooksRoot, _globalHooksRootEffectiveTime);
    1238
        }
    1239
    1240
        /// @notice Checks if the global hooks root is active (timelock period has passed)
    1241
        /// @return isActive True if the global hooks root is active
    1242 24×
        function isGlobalHooksRootActive() external view returns (bool) {
    1243
            return
    1244 0
                block.timestamp >= _globalHooksRootEffectiveTime &&
    1245 0
                _globalHooksRoot != bytes32(0);
    1246
        }
    1247
    1248
        /// @inheritdoc ISuperVaultAggregator
    1249 0
        function getStrategyHooksRoot(
    1250
            address strategy
    1251 0
        ) external view returns (bytes32 root) {
    1252 0
            return _strategyData[strategy].managerHooksRoot;
    1253
        }
    1254
    1255
        /// @inheritdoc ISuperVaultAggregator
    1256 0
        function getProposedStrategyHooksRoot(
    1257
            address strategy
    1258 0
        ) external view returns (bytes32 root, uint256 effectiveTime) {
    1259
            return (
    1260 0
                _strategyData[strategy].proposedHooksRoot,
    1261 0
                _strategyData[strategy].hooksRootEffectiveTime
    1262
            );
    1263
        }
    1264
    1265
        /*//////////////////////////////////////////////////////////////
    1266
                             INTERNAL HELPER FUNCTIONS
    1267
        //////////////////////////////////////////////////////////////*/
    1268
        /// @notice Internal implementation of forwarding PPS updates
    1269
        /// @param args Struct containing all parameters for PPS update
    1270 12×
        function _forwardPPS(PPSUpdateData memory args) internal {
    1271
            // Check rate limiting
    1272 20×
            uint256 minInterval = _strategyData[args.strategy].minUpdateInterval;
    1273 13×
            uint256 lastUpdate = _strategyData[args.strategy].lastUpdateTimestamp;
    1274 10×
            if (block.timestamp - lastUpdate < minInterval) {
    1275
                emit UpdateTooFrequent();
    1276
                return;
    1277
            }
    1278
    1279
            // Ensure timestamp is monotonically increasing to prevent out-of-order updates
    1280
            if (args.timestamp <= lastUpdate) {
    1281
                emit TimestampNotMonotonic();
    1282
                return;
    1283
            }
    1284
    1285
            // Get the strategy's manager to deduct upkeep cost from
    1286 27×
            address manager = _strategyData[args.strategy].mainManager;
    1287
    1288
            // Flag to track if any check failed
    1289
            bool checksFailed = false;
    1290
    1291
            // C2.1) Dispersion Check: Check if the standard deviation is too high relative to mean
    1292
            if (
    1293 20×
                _strategyData[args.strategy].dispersionThreshold !=
    1294
                type(uint256).max &&
    1295
                args.pps > 0
    1296
            ) {
    1297
                // Calculate dispersion as stddev/mean
    1298 24×
                uint256 dispersion = (args.ppsStdev * 1e18) / args.pps; // Scaled by 1e18 for precision
    1299 22×
                if (dispersion > _strategyData[args.strategy].dispersionThreshold) {
    1300 0
                    checksFailed = true;
    1301 0
                    emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DISPERSION");
    1302
                }
    1303
            }
    1304
    1305
            // C2.2) Deviation Check: Check if new PPS deviates too much from current PPS
    1306 17×
            uint256 currentPPS = _strategyData[args.strategy].pps;
    1307
            if (
    1308 20×
                _strategyData[args.strategy].deviationThreshold !=
    1309
                type(uint256).max &&
    1310
                currentPPS > 0
    1311
            ) {
    1312
                // Calculate absolute deviation, scaled by 1e18
    1313 14×
                uint256 absDiff = args.pps > currentPPS
    1314 11×
                    ? (args.pps - currentPPS)
    1315 10×
                    : (currentPPS - args.pps);
    1316 15×
                uint256 relativeDeviation = (absDiff * 1e18) / currentPPS;
    1317
                if (
    1318
                    relativeDeviation >
    1319 16×
                    _strategyData[args.strategy].deviationThreshold
    1320
                ) {
    1321
                    checksFailed = true;
    1322 18×
                    emit StrategyCheckFailed(args.strategy, "HIGH_PPS_DEVIATION");
    1323
                }
    1324
            }
    1325
    1326
            // C2.3) M/N Check: Check if enough validators participated
    1327
            if (
    1328 11×
                args.totalValidators > 0 &&
    1329 0
                _strategyData[args.strategy].mnThreshold > 0
    1330 0
            ) {
    1331
                // Calculate participation rate, scaled by 1e18
    1332 0
                uint256 participationRate = (args.validatorSet * 1e18) /
    1333 0
                    args.totalValidators;
    1334 0
                if (participationRate < _strategyData[args.strategy].mnThreshold) {
    1335 0
                    checksFailed = true;
    1336 0
                    emit StrategyCheckFailed(
    1337 0
                        args.strategy,
    1338
                        "INSUFFICIENT_VALIDATOR_PARTICIPATION"
    1339
                    );
    1340
                }
    1341
            }
    1342
    1343
            // Pause strategy if any check failed
    1344 31×
            if (checksFailed && !_strategyData[args.strategy].isPaused) {
    1345 30×
                _strategyData[args.strategy].isPaused = true;
    1346 10×
                emit StrategyPaused(args.strategy);
    1347
            }
    1348
            // Unpause strategy if all checks passed and strategy was previously paused
    1349 29×
            else if (!checksFailed && _strategyData[args.strategy].isPaused) {
    1350 24×
                _strategyData[args.strategy].isPaused = false;
    1351 10×
                emit StrategyUnpaused(args.strategy);
    1352
            }
    1353
    1354
            // Handle upkeep costs unless exempt
    1355
            if (!args.isExempt) {
    1356
                // Check if manager has sufficient upkeep balance
    1357 0
                if (_managerUpkeepBalance[manager] < args.upkeepCost) {
    1358 0
                    emit InsufficientUpkeep(
    1359 0
                        args.strategy,
    1360
                        manager,
    1361 0
                        _managerUpkeepBalance[manager],
    1362 0
                        args.upkeepCost
    1363
                    );
    1364 0
                    return;
    1365
                }
    1366
    1367
                // Deduct the upkeep cost and emit event
    1368 0
                _managerUpkeepBalance[manager] -= args.upkeepCost;
    1369
    1370
                // Add claimable upkeep for the `feeRecipient`
    1371 0
                claimableUpkeep += args.upkeepCost;
    1372
    1373 0
                emit UpkeepSpent(manager, args.upkeepCost);
    1374
            }
    1375
    1376
            // Update PPS, ppsStdev and timestamp in StrategyData
    1377 27×
            _strategyData[args.strategy].pps = args.pps;
    1378 21×
            _strategyData[args.strategy].ppsStdev = args.ppsStdev;
    1379 21×
            _strategyData[args.strategy].lastUpdateTimestamp = args.timestamp;
    1380
    1381 19×
            emit PPSUpdated(
    1382
                args.strategy,
    1383
                args.pps,
    1384
                args.ppsStdev,
    1385
                args.validatorSet,
    1386
                args.totalValidators,
    1387
                args.timestamp
    1388
            );
    1389
        }
    1390
    1391
        /// @notice Check if an update authority is exempt from paying upkeep costs
    1392
        /// @param strategy Address of the strategy being updated
    1393
        /// @param updateAuthority Address initiating the update
    1394
        /// @param timestamp Timestamp of the PPS measurement
    1395
        /// @return isExempt True if the authority is exempt from paying upkeep
    1396
        function _isExemptFromUpkeep(
    1397
            address strategy,
    1398
            address updateAuthority,
    1399
            uint256 timestamp
    1400
        ) internal returns (bool) {
    1401
            // Check if upkeep payments are globally disabled in SuperGovernor
    1402
            if (!SUPER_GOVERNOR.isUpkeepPaymentsEnabled()) {
    1403
                return true;
    1404
            }
    1405
    1406
            // Update is exempt if it is stale
    1407
            if (
    1408
                block.timestamp - timestamp > _strategyData[strategy].maxStaleness
    1409
            ) {
    1410
                emit StaleUpdate(strategy, updateAuthority, timestamp);
    1411
                return true;
    1412
            }
    1413
    1414
            // If manager is a superform manager, they're exempt from upkeep fees
    1415
            address manager = _strategyData[strategy].mainManager;
    1416
            if (SUPER_GOVERNOR.isSuperformManager(manager)) {
    1417
                return true;
    1418
            }
    1419
    1420
            // Check if the updateAuthority is in the authorized callers list
    1421
            // These are manager-designated keepers that should be exempt from fees
    1422
            // NOTE: Protected keepers cannot be added to this list (blocked in addAuthorizedCaller)
    1423
            if (
    1424
                _strategyData[strategy].authorizedCallers.contains(updateAuthority)
    1425
            ) {
    1426
                return true;
    1427
            }
    1428
    1429
            return false;
    1430
        }
    1431
    1432
        /// @notice Creates a leaf node for Merkle verification from hook address and arguments
    1433
        /// @param hookAddress The address of the hook contract
    1434
        /// @param hookArgs The packed-encoded hook arguments (from solidityPack in JS)
    1435
        /// @return leaf The leaf node hash
    1436 0
        function _createLeaf(
    1437
            address hookAddress,
    1438
            bytes calldata hookArgs
    1439 0
        ) internal pure returns (bytes32) {
    1440
            /// @dev The leaf now includes both hook address and args to prevent cross-hook replay attacks
    1441
            /// @dev Different hooks with identical encoded args will have different authorization leaves
    1442
            /// @dev This matches StandardMerkleTree's standardLeafHash: keccak256(keccak256(abi.encode(hookAddress,
    1443
            /// hookArgs)))
    1444
            /// @dev but uses bytes.concat for explicit concatenation
    1445 0
            return
    1446 0
                keccak256(
    1447 0
                    bytes.concat(keccak256(abi.encode(hookAddress, hookArgs)))
    1448
                );
    1449
        }
    1450
    1451
        /**
    1452
         * @dev Internal function to validate a single hook against either global or strategy root
    1453
         * @param hookAddress The address of the hook contract
    1454
         * @param hookArgs Hook arguments
    1455
         * @param proof Merkle proof for the specified root
    1456
         * @param isGlobalProof Whether to validate against global root (true) or strategy root (false)
    1457
         * @param cache Cached hook validation state variables
    1458
         * @param strategy Address of the strategy (needed to check banned leaves for global proofs)
    1459
         * @return True if hook is valid, false otherwise
    1460
         */
    1461 0
        function _validateSingleHook(
    1462
            address hookAddress,
    1463
            bytes calldata hookArgs,
    1464
            bytes32[] calldata proof,
    1465
            bool isGlobalProof,
    1466
            HookValidationCache memory cache,
    1467
            address strategy
    1468 0
        ) internal view returns (bool) {
    1469
            // Create leaf node from the hook address and arguments
    1470 0
            bytes32 leaf = _createLeaf(hookAddress, hookArgs);
    1471
    1472 0
            if (isGlobalProof) {
    1473
                // Validate against global root
    1474 0
                if (
    1475 0
                    cache.globalHooksRootVetoed ||
    1476 0
                    cache.globalHooksRoot == bytes32(0)
    1477
                ) {
    1478 0
                    return false;
    1479
                }
    1480
    1481
                // Check if this leaf is banned by the manager
    1482 0
                if (_strategyData[strategy].bannedLeaves[leaf]) {
    1483 0
                    return false;
    1484
                }
    1485
    1486
                // For single-leaf trees, empty proof is valid when root equals leaf
    1487 0
                if (proof.length == 0) {
    1488 0
                    return cache.globalHooksRoot == leaf;
    1489
                }
    1490 0
                return MerkleProof.verify(proof, cache.globalHooksRoot, leaf);
    1491
            } else {
    1492
                // Validate against strategy root
    1493 0
                if (
    1494 0
                    cache.strategyHooksRootVetoed ||
    1495 0
                    cache.strategyRoot == bytes32(0)
    1496
                ) {
    1497 0
                    return false;
    1498
                }
    1499
                // For single-leaf trees, empty proof is valid when root equals leaf
    1500 0
                if (proof.length == 0) {
    1501 0
                    return cache.strategyRoot == leaf;
    1502
                }
    1503 0
                return MerkleProof.verify(proof, cache.strategyRoot, leaf);
    1504
            }
    1505
        }
    1506
    1507
        /**
    1508
         * @dev Internal function to return the `SuperBank` address
    1509
         * @return superBank The superBank address
    1510
         */
    1511
        function _getSuperBank() internal view returns (address) {
    1512 118×
            return SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_BANK());
    1513
        }
    1514
    }
    80% src/SuperVault/SuperVaultEscrow.sol
    Lines covered: 12 / 15 (80%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    5
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    6
    7
    /// @title SuperVaultEscrow
    8
    /// @author Superform Labs
    9
    /// @notice Escrow contract for SuperVault shares during request/claim process
    10 229×
    contract SuperVaultEscrow {
    11
        using SafeERC20 for IERC20;
    12
    13
        /*//////////////////////////////////////////////////////////////
    14
                                    ERRORS
    15
        //////////////////////////////////////////////////////////////*/
    16
        error ALREADY_INITIALIZED();
    17
        error UNAUTHORIZED();
    18
        error ZERO_ADDRESS();
    19
    20
        /*//////////////////////////////////////////////////////////////
    21
                                    STATE
    22
        //////////////////////////////////////////////////////////////*/
    23 0
        bool public initialized;
    24 0
        address public vault;
    25 0
        address public strategy;
    26
    27
        /*//////////////////////////////////////////////////////////////
    28
                                    MODIFIERS
    29
        //////////////////////////////////////////////////////////////*/
    30
        modifier onlyVault() {
    31 68×
            if (msg.sender != vault) revert UNAUTHORIZED();
    32
            _;
    33
        }
    34
    35
        /*//////////////////////////////////////////////////////////////
    36
                                INITIALIZATION
    37
        //////////////////////////////////////////////////////////////*/
    38
    39
        /// @notice Initialize the escrow with required parameters
    40
        /// @param vaultAddress The vault contract address
    41
        /// @param strategyAddress The strategy contract address
    42 23×
        function initialize(address vaultAddress, address strategyAddress) external {
    43 28×
            if (initialized) revert ALREADY_INITIALIZED();
    44 14×
            if (vaultAddress == address(0) || strategyAddress == address(0)) revert ZERO_ADDRESS();
    45
    46
            initialized = true;
    47 13×
            vault = vaultAddress;
    48 10×
            strategy = strategyAddress;
    49
        }
    50
    51
        /*//////////////////////////////////////////////////////////////
    52
                                VAULT FUNCTIONS
    53
        //////////////////////////////////////////////////////////////*/
    54
    55
        /// @notice Transfer shares from user to escrow during redeem request
    56
        /// @param from The address to transfer shares from
    57
        /// @param amount The amount of shares to transfer
    58 29×
        function escrowShares(address from, uint256 amount) external onlyVault {
    59 14×
            IERC20(vault).safeTransferFrom(from, address(this), amount);
    60
        }
    61
    62
        /// @notice Return shares from escrow to user during redeem cancellation
    63
        /// @param to The address to return shares to
    64
        /// @param amount The amount of shares to return
    65 22×
        function returnShares(address to, uint256 amount) external onlyVault {
    66 12×
            IERC20(vault).safeTransfer(to, amount);
    67
        }
    68
    }
    84% src/SuperVault/SuperVaultStrategy.sol
    Lines covered: 557 / 661 (84%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // External
    5
    import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    6
    import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
    7
    import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
    8
    import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    9
    import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    10
    import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
    11
    import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    12
    import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    13
    14
    // Core Interfaces
    15
    import {ISuperHook, ISuperHookResult, ISuperHookOutflow, ISuperHookInflowOutflow, ISuperHookResultOutflow, ISuperHookContextAware, ISuperHookInspector} from "@superform-v2-core/src/interfaces/ISuperHook.sol";
    16
    import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
    17
    18
    // Periphery Interfaces
    19
    import {ISuperVault} from "../interfaces/SuperVault/ISuperVault.sol";
    20
    import {HookDataDecoder} from "@superform-v2-core/src/libraries/HookDataDecoder.sol";
    21
    import {ISuperVaultStrategy} from "../interfaces/SuperVault/ISuperVaultStrategy.sol";
    22
    import {ISuperGovernor, FeeType} from "../interfaces/ISuperGovernor.sol";
    23
    import {ISuperVaultAggregator} from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
    24
    25
    /// @title SuperVaultStrategy
    26
    /// @author Superform Labs
    27
    /// @notice Strategy implementation for SuperVault that executes strategies
    28 853×
    contract SuperVaultStrategy is
    29
        ISuperVaultStrategy,
    30
        Initializable,
    31
        ReentrancyGuardUpgradeable
    32
    {
    33
        using EnumerableSet for EnumerableSet.AddressSet;
    34
        using SafeERC20 for IERC20;
    35
        using Math for uint256;
    36
    37
        /*//////////////////////////////////////////////////////////////
    38
                                    CONSTANTS
    39
        //////////////////////////////////////////////////////////////*/
    40
        uint256 private constant ONE_WEEK = 7 days;
    41 17×
        uint256 private constant BPS_PRECISION = 10_000;
    42
        /// @dev The following is needed because the `processedShares` for some vaults (for example Centrifuge)
    43
        ///      can be lower by 1 or 2 wei than the `totalRequestedAmount`in SuperVault shares
    44
        uint256 private constant TOLERANCE_CONSTANT = 10 wei;
    45
    46
        // Slippage tolerance in BPS (1%)
    47
        uint256 private constant SV_SLIPPAGE_TOLERANCE_BPS = 100;
    48
    49 0
        uint256 public PRECISION;
    50
    51
        /*//////////////////////////////////////////////////////////////
    52
                                    STATE
    53
        //////////////////////////////////////////////////////////////*/
    54
        address private _vault;
    55
        IERC20 private _asset;
    56
        uint8 private _vaultDecimals;
    57
    58
        // Global configuration
    59
        uint256 private _maxPPSSlippage;
    60
    61
        // Fee configuration
    62
        FeeConfig private feeConfig;
    63
        FeeConfig private proposedFeeConfig;
    64
        uint256 private feeConfigEffectiveTime;
    65
    66
        // Core contracts
    67 0
        ISuperGovernor public immutable superGovernor;
    68
    69
        // Emergency withdrawable configuration
    70 0
        bool public emergencyWithdrawable;
    71 0
        bool public proposedEmergencyWithdrawable;
    72 0
        uint256 public emergencyWithdrawableEffectiveTime;
    73
    74
        // Yield source configuration - simplified mapping from source to oracle
    75
        mapping(address source => address oracle) private yieldSources;
    76
        EnumerableSet.AddressSet private yieldSourcesList;
    77
    78
        // --- Redeem Request State ---
    79
        mapping(address controller => SuperVaultState state)
    80
            private superVaultState;
    81
    82 28×
        constructor(address superGovernor_) {
    83
            if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
    84
    85
            superGovernor = ISuperGovernor(superGovernor_);
    86
            emit SuperGovernorSet(superGovernor_);
    87
            _disableInitializers();
    88
        }
    89
    90
        /// @notice Allows the contract to receive native ETH
    91
        /// @dev Required for hooks that may send ETH back to the strategy
    92
        receive() external payable {}
    93
    94
        /*//////////////////////////////////////////////////////////////
    95
                                INITIALIZATION
    96
        //////////////////////////////////////////////////////////////*/
    97 20×
        function initialize(
    98
            address vaultAddress,
    99
            FeeConfig memory feeConfigData
    100
        ) external initializer {
    101
            if (vaultAddress == address(0)) revert INVALID_VAULT();
    102
            // if either fee is configured, check if recipient is address (0), if it is revert with ZERO ADDRESS
    103
            // if both fees are 0, no need check address (it just passes the if). Recipient can be configured later
    104
            if (
    105 14×
                (feeConfigData.performanceFeeBps > 0 ||
    106
                    feeConfigData.managementFeeBps > 0) &&
    107
                feeConfigData.recipient == address(0)
    108 0
            ) revert ZERO_ADDRESS();
    109
            if (feeConfigData.performanceFeeBps > BPS_PRECISION)
    110 13×
                revert INVALID_PERFORMANCE_FEE_BPS();
    111
            if (feeConfigData.managementFeeBps > BPS_PRECISION)
    112 13×
                revert INVALID_PERFORMANCE_FEE_BPS();
    113
    114
            __ReentrancyGuard_init();
    115
    116 13×
            _vault = vaultAddress;
    117 78×
            _asset = IERC20(IERC4626(vaultAddress).asset());
    118 76×
            _vaultDecimals = IERC20Metadata(vaultAddress).decimals();
    119 13×
            PRECISION = 10 ** _vaultDecimals;
    120 25×
            feeConfig = feeConfigData;
    121
            _maxPPSSlippage = 500; // 5% as a start, configurable later
    122
    123
            emit Initialized(_vault);
    124
        }
    125
    126
        /*//////////////////////////////////////////////////////////////
    127
                            CORE STRATEGY OPERATIONS
    128
        //////////////////////////////////////////////////////////////*/
    129
    130
        /// @inheritdoc ISuperVaultStrategy
    131 119×
        function handleOperations4626Deposit(
    132
            address controller,
    133
            uint256 assetsGross
    134
        ) external returns (uint256 sharesNet) {
    135 11×
            _requireVault();
    136
    137 12×
            if (assetsGross == 0) revert INVALID_AMOUNT();
    138 10×
            if (controller == address(0)) revert ZERO_ADDRESS();
    139
    140
            // Check if strategy is paused or if global hooks root is vetoed
    141 42×
            if (_isPaused()) revert STRATEGY_PAUSED();
    142 130×
            if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) {
    143 0
                revert OPERATIONS_BLOCKED_BY_VETO();
    144
            }
    145
    146
            // Fee skim in ASSETS (asset-side entry fee)
    147
            uint256 feeBps = feeConfig.managementFeeBps;
    148 20×
            uint256 feeAssets = feeBps == 0
    149
                ? 0
    150
                : Math.mulDiv(
    151
                    assetsGross,
    152
                    feeBps,
    153
                    BPS_PRECISION,
    154
                    Math.Rounding.Ceil
    155
                );
    156
    157 16×
            uint256 assetsNet = assetsGross - feeAssets;
    158 38×
            if (assetsNet == 0) revert INVALID_AMOUNT();
    159
    160 12×
            if (feeAssets != 0) {
    161
                address recipient = feeConfig.recipient;
    162
                if (recipient == address(0)) revert ZERO_ADDRESS();
    163 20×
                _safeTokenTransfer(address(_asset), recipient, feeAssets);
    164 42×
                emit ManagementFeePaid(controller, recipient, feeAssets, feeBps);
    165
            }
    166
    167
            // Compute shares on NET using current PPS
    168 14×
            uint256 pps = getStoredPPS();
    169 38×
            if (pps == 0) revert INVALID_PPS();
    170 22×
            sharesNet = Math.mulDiv(assetsNet, PRECISION, pps, Math.Rounding.Floor);
    171 38×
            if (sharesNet == 0) revert INVALID_AMOUNT();
    172
    173
            // Account on NET
    174 28×
            SuperVaultState storage state = superVaultState[controller];
    175 42×
            state.accumulatorShares += sharesNet;
    176 34×
            state.accumulatorCostBasis += assetsNet;
    177 30×
            emit DepositHandled(controller, assetsNet, sharesNet);
    178 10×
            return sharesNet;
    179
        }
    180
    181
        /// @inheritdoc ISuperVaultStrategy
    182 61×
        function handleOperations4626Mint(
    183
            address controller,
    184
            uint256 sharesNet,
    185
            uint256 assetsGross,
    186
            uint256 assetsNet
    187
        ) external {
    188 11×
            _requireVault();
    189
    190 12×
            if (sharesNet == 0) revert INVALID_AMOUNT();
    191 10×
            if (controller == address(0)) revert ZERO_ADDRESS();
    192
    193
            // Check if strategy is paused or if global hooks root is vetoed
    194 42×
            if (_isPaused()) revert STRATEGY_PAUSED();
    195 130×
            if (_getSuperVaultAggregator().isGlobalHooksRootVetoed()) {
    196 0
                revert OPERATIONS_BLOCKED_BY_VETO();
    197
            }
    198
    199
            uint256 feeBps = feeConfig.managementFeeBps;
    200
            // Transfer fee if needed
    201 12×
            if (feeBps != 0) {
    202 16×
                uint256 feeAssets = assetsGross - assetsNet;
    203 12×
                if (feeAssets != 0) {
    204
                    address recipient = feeConfig.recipient;
    205
                    if (recipient == address(0)) revert ZERO_ADDRESS();
    206 20×
                    _safeTokenTransfer(address(_asset), recipient, feeAssets);
    207 34×
                    emit ManagementFeePaid(
    208
                        controller,
    209
                        recipient,
    210
                        feeAssets,
    211
                        feeBps
    212
                    );
    213
                }
    214
            }
    215
    216
            // Account on NET
    217 28×
            SuperVaultState storage state = superVaultState[controller];
    218 42×
            state.accumulatorShares += sharesNet;
    219 34×
            state.accumulatorCostBasis += assetsNet;
    220 30×
            emit DepositHandled(controller, assetsNet, sharesNet);
    221
        }
    222
    223
        /// @inheritdoc ISuperVaultStrategy
    224 60×
        function quoteMintAssetsGross(
    225
            uint256 shares
    226
        ) external view returns (uint256 assetsGross, uint256 assetsNet) {
    227 14×
            uint256 pps = getStoredPPS();
    228 38×
            if (pps == 0) revert INVALID_PPS();
    229 22×
            assetsNet = Math.mulDiv(shares, pps, PRECISION, Math.Rounding.Ceil);
    230 12×
            if (assetsNet == 0) revert INVALID_AMOUNT();
    231
    232
            uint256 feeBps = feeConfig.managementFeeBps;
    233 22×
            if (feeBps == 0) return (assetsNet, assetsNet);
    234 36×
            if (feeBps >= BPS_PRECISION) revert INVALID_AMOUNT(); // prevents div-by-zero (100% fee)
    235 12×
            assetsGross = Math.mulDiv(
    236
                assetsNet,
    237
                BPS_PRECISION,
    238 10×
                (BPS_PRECISION - feeBps),
    239
                Math.Rounding.Ceil
    240
            );
    241
            return (assetsGross, assetsNet);
    242
        }
    243
    244
        /// @inheritdoc ISuperVaultStrategy
    245 44×
        function handleOperations7540(
    246
            Operation operation,
    247
            address controller,
    248
            address receiver,
    249
            uint256 amount
    250
        ) external {
    251
            _requireVault();
    252 15×
            if (operation == Operation.RedeemRequest) {
    253
                _handleRequestRedeem(controller, amount); // amount = shares
    254 13×
            } else if (operation == Operation.CancelRedeem) {
    255
                _handleCancelRedeem(controller);
    256 15×
            } else if (operation == Operation.ClaimRedeem) {
    257
                _handleClaimRedeem(controller, receiver, amount); // amount = assets
    258
            } else {
    259 13×
                revert ACTION_TYPE_DISALLOWED();
    260
            }
    261
        }
    262
    263
        /*//////////////////////////////////////////////////////////////
    264
                    MANAGER EXTERNAL ACCESS FUNCTIONS
    265
        //////////////////////////////////////////////////////////////*/
    266
    267
        /// @inheritdoc ISuperVaultStrategy
    268 22×
        function executeHooks(
    269
            ExecuteArgs calldata args
    270
        ) external payable nonReentrant {
    271 10×
            _isManager(msg.sender);
    272
    273 22×
            uint256 hooksLength = args.hooks.length;
    274 12×
            if (hooksLength == 0) revert ZERO_LENGTH();
    275 30×
            if (args.hookCalldata.length != hooksLength)
    276 13×
                revert INVALID_ARRAY_LENGTH();
    277 30×
            if (args.expectedAssetsOrSharesOut.length != hooksLength)
    278 13×
                revert INVALID_ARRAY_LENGTH();
    279 30×
            if (args.globalProofs.length != hooksLength)
    280 13×
                revert INVALID_ARRAY_LENGTH();
    281 30×
            if (args.strategyProofs.length != hooksLength)
    282 13×
                revert INVALID_ARRAY_LENGTH();
    283
    284
            address prevHook;
    285 21×
            for (uint256 i; i < hooksLength; ++i) {
    286 62×
                address hook = args.hooks[i];
    287 29×
                if (!_isRegisteredHook(hook)) revert INVALID_HOOK();
    288
    289
                // Check if the hook was validated
    290
                if (
    291 162×
                    !_validateHook(
    292
                        hook,
    293 56×
                        args.hookCalldata[i],
    294 58×
                        args.globalProofs[i],
    295 16×
                        args.strategyProofs[i]
    296
                    )
    297
                ) {
    298 0
                    revert HOOK_VALIDATION_FAILED();
    299
                }
    300
    301 90×
                prevHook = _processSingleHookExecution(
    302
                    hook,
    303
                    prevHook,
    304 56×
                    args.hookCalldata[i],
    305 44×
                    args.expectedAssetsOrSharesOut[i]
    306
                );
    307
            }
    308 23×
            emit HooksExecuted(args.hooks);
    309
        }
    310
    311
        /// @inheritdoc ISuperVaultStrategy
    312 54×
        function fulfillRedeemRequests(
    313
            FulfillArgs calldata args
    314
        ) external nonReentrant {
    315 10×
            _isManager(msg.sender);
    316
    317
            // Check if strategy is paused
    318 42×
            if (_isPaused()) revert STRATEGY_PAUSED();
    319
    320 26×
            uint256 hooksLength = args.hooks.length;
    321 12×
            if (hooksLength == 0) revert ZERO_LENGTH();
    322 22×
            uint256 controllersLength = args.controllers.length;
    323 12×
            if (controllersLength == 0) revert ZERO_LENGTH();
    324 30×
            if (args.hookCalldata.length != hooksLength)
    325 13×
                revert INVALID_ARRAY_LENGTH();
    326 30×
            if (args.expectedAssetsOrSharesOut.length != hooksLength)
    327 13×
                revert INVALID_ARRAY_LENGTH();
    328 30×
            if (args.globalProofs.length != hooksLength)
    329 13×
                revert INVALID_ARRAY_LENGTH();
    330 30×
            if (args.strategyProofs.length != hooksLength)
    331 13×
                revert INVALID_ARRAY_LENGTH();
    332
    333 14×
            uint256 currentPPS = getStoredPPS();
    334 38×
            if (currentPPS == 0) revert INVALID_PPS();
    335
    336
            // Pre-calculate totals to ensure no overburn of escrowed shares
    337
            uint256 totalRequestedShares;
    338 28×
            for (uint256 i; i < controllersLength; ++i) {
    339 102×
                totalRequestedShares += superVaultState[args.controllers[i]]
    340
                    .pendingRedeemRequest;
    341
            }
    342
            uint256 intendedShares;
    343 26×
            for (uint256 i; i < hooksLength; ++i) {
    344 66×
                address hook = args.hooks[i];
    345 29×
                if (!_isFulfillRequestsHook(hook)) revert INVALID_HOOK();
    346
                // Check if the hook was validated
    347
                if (
    348 244×
                    !_validateHook(
    349
                        hook,
    350 56×
                        args.hookCalldata[i],
    351 58×
                        args.globalProofs[i],
    352 58×
                        args.strategyProofs[i]
    353
                    )
    354
                ) {
    355 0
                    revert HOOK_VALIDATION_FAILED();
    356
                }
    357 52×
                if (args.expectedAssetsOrSharesOut[i] == 0)
    358 26×
                    revert ZERO_EXPECTED_VALUE();
    359
    360 158×
                intendedShares += ISuperHookInflowOutflow(hook).decodeAmount(
    361 56×
                    args.hookCalldata[i]
    362
                );
    363
            }
    364
    365
            // Enforce both lower- and upper-bounds on intended shares to prevent escrow overburn
    366 22×
            if (intendedShares + TOLERANCE_CONSTANT < totalRequestedShares) {
    367 26×
                revert INVALID_REDEEM_FILL();
    368
            }
    369
    370 22×
            if (intendedShares > totalRequestedShares + TOLERANCE_CONSTANT) {
    371 13×
                revert INVALID_REDEEM_FILL();
    372
            }
    373
    374
            uint256 processedShares;
    375 27×
            for (uint256 i; i < hooksLength; ++i) {
    376 66×
                address hook = args.hooks[i];
    377 93×
                uint256 amountSharesSpent = _processSingleFulfillHookExecution(
    378
                    hook,
    379 56×
                    args.hookCalldata[i],
    380 44×
                    args.expectedAssetsOrSharesOut[i],
    381
                    currentPPS
    382
                );
    383
                processedShares += amountSharesSpent;
    384
            }
    385
    386
            // Post-condition: processed shares must match intended shares
    387
            if (processedShares != intendedShares) revert INVALID_REDEEM_FILL();
    388
    389
            _processRedeemFulfillments(
    390
                args.controllers,
    391
                controllersLength,
    392
                processedShares,
    393
                currentPPS
    394
            );
    395
    396 41×
            ISuperVault(_vault).burnShares(processedShares);
    397
    398 22×
            emit RedeemRequestsFulfilled(
    399
                args.hooks,
    400
                args.controllers,
    401
                processedShares,
    402
                currentPPS
    403
            );
    404
        }
    405
    406
        /*//////////////////////////////////////////////////////////////
    407
                            YIELD SOURCE MANAGEMENT
    408
        //////////////////////////////////////////////////////////////*/
    409
    410
        // @inheritdoc ISuperVaultStrategy
    411 29×
        function manageYieldSource(
    412
            address source,
    413
            address oracle,
    414
            uint8 actionType
    415
        ) external {
    416
            _isPrimaryManager(msg.sender);
    417
            _manageYieldSource(source, oracle, actionType);
    418
        }
    419
    420
        // @inheritdoc ISuperVaultStrategy
    421 24×
        function manageYieldSources(
    422
            address[] calldata sources,
    423
            address[] calldata oracles,
    424
            uint8[] calldata actionTypes
    425
        ) external {
    426
            _isPrimaryManager(msg.sender);
    427
    428
            uint256 length = sources.length;
    429
            if (length == 0) revert ZERO_LENGTH();
    430 19×
            if (oracles.length != length) revert INVALID_ARRAY_LENGTH();
    431 19×
            if (actionTypes.length != length) revert INVALID_ARRAY_LENGTH();
    432
    433 14×
            for (uint256 i; i < length; ++i) {
    434 76×
                _manageYieldSource(sources[i], oracles[i], actionTypes[i]);
    435
            }
    436
        }
    437
    438
        // @inheritdoc ISuperVaultStrategy
    439 25×
        function proposeVaultFeeConfigUpdate(
    440
            uint256 performanceFeeBps,
    441
            uint256 managementFeeBps,
    442
            address recipient
    443
        ) external {
    444
            _isPrimaryManager(msg.sender);
    445
    446
            if (performanceFeeBps > BPS_PRECISION)
    447 13×
                revert INVALID_PERFORMANCE_FEE_BPS();
    448
            if (managementFeeBps > BPS_PRECISION)
    449 13×
                revert INVALID_PERFORMANCE_FEE_BPS();
    450 18×
            if (recipient == address(0)) revert ZERO_ADDRESS();
    451 41×
            proposedFeeConfig = FeeConfig({
    452
                performanceFeeBps: performanceFeeBps,
    453
                managementFeeBps: managementFeeBps,
    454
                recipient: recipient
    455
            });
    456
            feeConfigEffectiveTime = block.timestamp + ONE_WEEK;
    457 24×
            emit VaultFeeConfigProposed(
    458
                performanceFeeBps,
    459
                managementFeeBps,
    460
                recipient,
    461
                feeConfigEffectiveTime
    462
            );
    463
        }
    464
    465
        // @inheritdoc ISuperVaultStrategy
    466 13×
        function executeVaultFeeConfigUpdate() external {
    467
            if (block.timestamp < feeConfigEffectiveTime)
    468 13×
                revert INVALID_TIMESTAMP();
    469 19×
            if (proposedFeeConfig.recipient == address(0)) revert ZERO_ADDRESS();
    470 31×
            feeConfig = proposedFeeConfig;
    471 12×
            delete proposedFeeConfig;
    472
            feeConfigEffectiveTime = 0;
    473 12×
            emit VaultFeeConfigUpdated(
    474
                feeConfig.performanceFeeBps,
    475
                feeConfig.managementFeeBps,
    476
                feeConfig.recipient
    477
            );
    478
        }
    479
    480
        // @inheritdoc ISuperVaultStrategy
    481 19×
        function updateMaxPPSSlippage(uint256 maxSlippageBps) external {
    482
            _isPrimaryManager(msg.sender);
    483 19×
            if (maxSlippageBps > BPS_PRECISION) revert INVALID_MAX_SLIPPAGE_BPS();
    484
            _maxPPSSlippage = maxSlippageBps;
    485 11×
            emit MaxPPSSlippageUpdated(maxSlippageBps);
    486
        }
    487
    488
        // @inheritdoc ISuperVaultStrategy
    489 17×
        function manageEmergencyWithdraw(
    490
            uint8 action,
    491
            address recipient,
    492
            uint256 amount
    493
        ) external {
    494
            if (action == 1) {
    495
                _proposeEmergencyWithdraw();
    496
            } else if (action == 2) {
    497
                _executeEmergencyWithdrawActivation();
    498
            } else if (action == 3) {
    499
                _performEmergencyWithdraw(recipient, amount);
    500
            } else if (action == 4) {
    501
                _cancelEmergencyWithdrawProposal();
    502
            } else {
    503
                revert ACTION_TYPE_DISALLOWED();
    504
            }
    505
        }
    506
    507
        /*//////////////////////////////////////////////////////////////
    508
                            ACCOUNTING MANAGEMENT
    509
        //////////////////////////////////////////////////////////////*/
    510
        /// @inheritdoc ISuperVaultStrategy
    511 81×
        function moveAccumulatorOnTransfer(
    512
            address from,
    513
            address to,
    514
            uint256 shares
    515
        ) external {
    516 15×
            _requireVault();
    517 18×
            if (shares == 0) return;
    518
    519 42×
            SuperVaultState storage fromState = superVaultState[from];
    520 27×
            SuperVaultState storage toState = superVaultState[to];
    521
    522
            // Only move accumulator proportional to what's available
    523
            // (accumulator shares may be less than ERC20 shares due to pending redeems)
    524
            // see test_Fix1_AuditAttackScenarioFails
    525 12×
            uint256 availableAccumulatorShares = fromState.accumulatorShares;
    526 25×
            if (availableAccumulatorShares == 0) return; // No accumulator to move
    527
    528 12×
            uint256 sharesToMove = shares > availableAccumulatorShares
    529
                ? availableAccumulatorShares
    530
                : shares;
    531
    532
            // Pro-rata move of cost basis (NO PPS here; preserves fee correctness)
    533 12×
            uint256 movedCostBasis = sharesToMove == availableAccumulatorShares
    534
                ? fromState.accumulatorCostBasis
    535
                : Math.mulDiv(
    536
                    sharesToMove,
    537
                    fromState.accumulatorCostBasis,
    538
                    availableAccumulatorShares,
    539
                    Math.Rounding.Floor
    540
                ); /// @audit is there a value st when transfered transfers minimal shares and maximum cost basis
    541
    542 21×
            fromState.accumulatorShares -= sharesToMove;
    543 21×
            fromState.accumulatorCostBasis -= movedCostBasis;
    544
    545 21×
            toState.accumulatorShares += sharesToMove;
    546 17×
            toState.accumulatorCostBasis += movedCostBasis;
    547
    548
            // Never touch: pendingRedeemRequest, averageRequestPPS, maxWithdraw, averageWithdrawPrice
    549
        }
    550
    551
        /*//////////////////////////////////////////////////////////////
    552
                                VIEW FUNCTIONS
    553
        //////////////////////////////////////////////////////////////*/
    554
    555
        // @inheritdoc ISuperVaultStrategy
    556 0
        function getVaultInfo()
    557
            external
    558
            view
    559
            returns (address vault, address asset, uint8 vaultDecimals)
    560
        {
    561 0
            vault = _vault;
    562 0
            asset = address(_asset);
    563 0
            vaultDecimals = _vaultDecimals;
    564
        }
    565
    566
        // @inheritdoc ISuperVaultStrategy
    567 36×
        function getConfigInfo()
    568
            external
    569
            view
    570
            returns (FeeConfig memory feeConfig_)
    571
        {
    572 58×
            feeConfig_ = feeConfig;
    573
        }
    574
    575
        // @inheritdoc ISuperVaultStrategy
    576 49×
        function getStoredPPS() public view returns (uint256) {
    577 260×
            return _getSuperVaultAggregator().getPPS(address(this));
    578
        }
    579
    580
        // @inheritdoc ISuperVaultStrategy
    581 48×
        function getSuperVaultState(
    582
            address controller
    583
        ) external view returns (SuperVaultState memory state) {
    584 148×
            return superVaultState[controller];
    585
        }
    586
    587
        // @inheritdoc ISuperVaultStrategy
    588 0
        function getYieldSource(
    589
            address source
    590
        ) external view returns (YieldSource memory) {
    591 0
            return YieldSource({oracle: yieldSources[source]});
    592
        }
    593
    594
        // @inheritdoc ISuperVaultStrategy
    595 0
        function getYieldSourcesList()
    596
            external
    597
            view
    598 0
            returns (YieldSourceInfo[] memory)
    599
        {
    600 0
            uint256 length = yieldSourcesList.length();
    601 0
            YieldSourceInfo[] memory sourcesInfo = new YieldSourceInfo[](length);
    602
    603 0
            for (uint256 i; i < length; ++i) {
    604 0
                address sourceAddress = yieldSourcesList.at(i);
    605 0
                address oracle = yieldSources[sourceAddress];
    606
    607 0
                sourcesInfo[i] = YieldSourceInfo({
    608
                    sourceAddress: sourceAddress,
    609
                    oracle: oracle
    610
                });
    611
            }
    612
    613 0
            return sourcesInfo;
    614
        }
    615
    616
        // @inheritdoc ISuperVaultStrategy
    617 0
        function getYieldSources() external view returns (address[] memory) {
    618 0
            return yieldSourcesList.values();
    619
        }
    620
    621
        // @inheritdoc ISuperVaultStrategy
    622 0
        function getYieldSourcesCount() external view returns (uint256) {
    623 0
            return yieldSourcesList.length();
    624
        }
    625
    626
        // @inheritdoc ISuperVaultStrategy
    627 0
        function containsYieldSource(address source) external view returns (bool) {
    628 0
            return yieldSourcesList.contains(source);
    629
        }
    630
    631
        // @inheritdoc ISuperVaultStrategy
    632 60×
        function pendingRedeemRequest(
    633
            address controller
    634
        ) external view returns (uint256 pendingShares) {
    635 48×
            return superVaultState[controller].pendingRedeemRequest;
    636
        }
    637
    638
        // @inheritdoc ISuperVaultStrategy
    639 30×
        function claimableWithdraw(
    640
            address controller
    641
        ) external view returns (uint256 claimableAssets) {
    642 28×
            return superVaultState[controller].maxWithdraw;
    643
        }
    644
    645
        // @inheritdoc ISuperVaultStrategy
    646 45×
        function getAverageWithdrawPrice(
    647
            address controller
    648
        ) external view returns (uint256 averageWithdrawPrice) {
    649 42×
            return superVaultState[controller].averageWithdrawPrice;
    650
        }
    651
    652
        // @inheritdoc ISuperVaultStrategy
    653 0
        function previewPerformanceFee(
    654
            address controller,
    655
            uint256 sharesToRedeem
    656
        )
    657
            external
    658
            view
    659 0
            returns (uint256 totalFee, uint256 superformFee, uint256 recipientFee)
    660
        {
    661 0
            if (sharesToRedeem == 0) return (0, 0, 0);
    662
    663
            // Get controller's state
    664 0
            SuperVaultState storage state = superVaultState[controller];
    665
    666
            // Check if controller has enough shares
    667 0
            if (sharesToRedeem > state.accumulatorShares) return (0, 0, 0);
    668
    669
            // Get the current price per share
    670 0
            uint256 currentPPS = getStoredPPS();
    671
    672
            // Calculate historical assets (cost basis)
    673 0
            uint256 historicalAssets = 0;
    674 0
            if (state.accumulatorShares > 0) {
    675 0
                historicalAssets = sharesToRedeem.mulDiv(
    676 0
                    state.accumulatorCostBasis,
    677 0
                    state.accumulatorShares,
    678 0
                    Math.Rounding.Floor
    679
                );
    680
            }
    681
    682
            // Calculate current value of shares in asset terms
    683 0
            uint256 currentAssetsWithFees = sharesToRedeem.mulDiv(
    684 0
                currentPPS,
    685 0
                PRECISION,
    686
                Math.Rounding.Floor
    687
            );
    688
    689
            // Calculate fee (if any) using same logic as _calculateAndTransferFee
    690 0
            if (currentAssetsWithFees > historicalAssets) {
    691 0
                uint256 profit = currentAssetsWithFees - historicalAssets;
    692 0
                uint256 performanceFeeBps = feeConfig.performanceFeeBps;
    693 0
                totalFee = profit.mulDiv(
    694
                    performanceFeeBps,
    695
                    BPS_PRECISION,
    696
                    Math.Rounding.Floor
    697
                );
    698
    699 0
                if (totalFee > 0) {
    700
                    // Calculate Superform's portion of the fee using revenueShare from SuperGovernor
    701 0
                    superformFee = totalFee.mulDiv(
    702 0
                        superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE),
    703
                        BPS_PRECISION,
    704 0
                        Math.Rounding.Floor
    705
                    );
    706 0
                    recipientFee = totalFee - superformFee;
    707
                }
    708
            }
    709
    710 0
            return (totalFee, superformFee, recipientFee);
    711
        }
    712
    713
        /*//////////////////////////////////////////////////////////////
    714
                            INTERNAL FUNCTIONS
    715
        //////////////////////////////////////////////////////////////*/
    716
    717
        /// @notice Process a single hook execution
    718
        /// @param hook Hook address
    719
        /// @param prevHook Previous hook address
    720
        /// @param hookCalldata Hook calldata
    721
        /// @param expectedAssetsOrSharesOut Expected assets or shares output
    722
        /// @return processedHook Processed hook address
    723
        function _processSingleHookExecution(
    724
            address hook,
    725
            address prevHook,
    726
            bytes memory hookCalldata,
    727
            uint256 expectedAssetsOrSharesOut
    728
        ) internal returns (address) {
    729
            ExecutionVars memory vars;
    730
            vars.hookContract = ISuperHook(hook);
    731
    732 28×
            vars.targetedYieldSource = HookDataDecoder.extractYieldSource(
    733
                hookCalldata
    734
            );
    735 30×
            if (yieldSources[vars.targetedYieldSource] == address(0))
    736 26×
                revert YIELD_SOURCE_NOT_FOUND();
    737
    738 14×
            bool usePrevHookAmount = _decodeHookUsePrevHookAmount(
    739
                hook,
    740
                hookCalldata
    741
            );
    742 28×
            if (usePrevHookAmount && prevHook != address(0)) {
    743 0
                vars.outAmount = _getPreviousHookOutAmount(prevHook);
    744 0
                if (expectedAssetsOrSharesOut == 0) revert ZERO_EXPECTED_VALUE();
    745 0
                uint256 minExpectedPrevOut = expectedAssetsOrSharesOut *
    746 0
                    (BPS_PRECISION - _getSlippageTolerance());
    747 0
                if (vars.outAmount * BPS_PRECISION < minExpectedPrevOut) {
    748 0
                    revert MINIMUM_PREVIOUS_HOOK_OUT_AMOUNT_NOT_MET();
    749
                }
    750
            }
    751
    752 94×
            ISuperHook(address(vars.hookContract)).setExecutionContext(
    753
                address(this)
    754
            );
    755 162×
            vars.executions = vars.hookContract.build(
    756
                prevHook,
    757
                address(this),
    758
                hookCalldata
    759
            );
    760 34×
            for (uint256 j; j < vars.executions.length; ++j) {
    761 174×
                (vars.success, ) = vars.executions[j].target.call{
    762 42×
                    value: vars.executions[j].value
    763 42×
                }(vars.executions[j].callData);
    764 32×
                if (!vars.success) revert OPERATION_FAILED();
    765
            }
    766 86×
            ISuperHook(address(vars.hookContract)).resetExecutionState(
    767
                address(this)
    768
            );
    769
    770 120×
            uint256 actualOutput = ISuperHookResult(hook).getOutAmount(
    771
                address(this)
    772
            );
    773 37×
            if (actualOutput == 0) revert ZERO_OUTPUT_AMOUNT();
    774
    775 15×
            uint256 minExpectedOut = (expectedAssetsOrSharesOut *
    776
                (BPS_PRECISION - _getSlippageTolerance())) / BPS_PRECISION;
    777
            if (actualOutput < minExpectedOut) {
    778 0
                revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET();
    779
            }
    780
    781 20×
            emit HookExecuted(
    782
                hook,
    783
                prevHook,
    784
                vars.targetedYieldSource,
    785
                usePrevHookAmount,
    786
                hookCalldata
    787
            );
    788
    789
            return hook;
    790
        }
    791
    792
        /// @notice Process a single hook fulfillment execution
    793
        /// @param hook Hook address
    794
        /// @param hookCalldata Hook calldata
    795
        /// @param expectedAssetOutput Expected asset output
    796
        /// @param currentPPS Current price per share
    797
        /// @return processedShares Processed shares
    798
        function _processSingleFulfillHookExecution(
    799
            address hook,
    800
            bytes memory hookCalldata,
    801
            uint256 expectedAssetOutput,
    802
            uint256 currentPPS
    803
        ) internal returns (uint256) {
    804
            OutflowExecutionVars memory vars;
    805 16×
            vars.hookContract = ISuperHook(hook);
    806 154×
            vars.hookType = ISuperHookResult(hook).hookType();
    807 32×
            if (vars.hookType != ISuperHook.HookType.OUTFLOW)
    808 0
                revert INVALID_HOOK_TYPE();
    809
    810 28×
            vars.targetedYieldSource = HookDataDecoder.extractYieldSource(
    811
                hookCalldata
    812
            );
    813 30×
            if (yieldSources[vars.targetedYieldSource] == address(0))
    814 26×
                revert YIELD_SOURCE_NOT_FOUND();
    815
    816
            // we must always encode supervault shares when fulfilling redemptions
    817 134×
            vars.superVaultShares = ISuperHookInflowOutflow(hook).decodeAmount(
    818
                hookCalldata
    819
            );
    820
    821
            // Calculate underlying shares and update hook calldata
    822 22×
            vars.amountOfAssets = vars.superVaultShares.mulDiv(
    823
                currentPPS,
    824
                PRECISION,
    825
                Math.Rounding.Floor
    826
            );
    827 24×
            vars.svAsset = address(_asset);
    828 132×
            vars.amountConvertedToUnderlyingShares = IYieldSourceOracle(
    829 46×
                yieldSources[vars.targetedYieldSource]
    830
            ).getShareOutput(
    831
                    vars.targetedYieldSource,
    832
                    vars.svAsset,
    833
                    vars.amountOfAssets
    834
                );
    835 126×
            hookCalldata = ISuperHookOutflow(hook).replaceCalldataAmount(
    836
                hookCalldata,
    837
                vars.amountConvertedToUnderlyingShares
    838
            );
    839
    840 26×
            vars.balanceAssetBefore = _getTokenBalance(vars.svAsset, address(this));
    841
    842 94×
            ISuperHook(address(vars.hookContract)).setExecutionContext(
    843
                address(this)
    844
            );
    845
    846 162×
            vars.executions = vars.hookContract.build(
    847
                address(0),
    848
                address(this),
    849
                hookCalldata
    850
            );
    851 33×
            for (uint256 j; j < vars.executions.length; ++j) {
    852 172×
                (vars.success, ) = vars.executions[j].target.call(
    853 42×
                    vars.executions[j].callData
    854
                );
    855 32×
                if (!vars.success) revert OPERATION_FAILED();
    856
            }
    857 47×
            ISuperHook(address(vars.hookContract)).resetExecutionState(
    858
                address(this)
    859
            );
    860
    861
            vars.outAmount =
    862 15×
                _getTokenBalance(vars.svAsset, address(this)) -
    863
                vars.balanceAssetBefore;
    864
    865 18×
            if (vars.outAmount == 0) revert ZERO_OUTPUT_AMOUNT();
    866
            if (
    867 11×
                vars.outAmount * BPS_PRECISION <
    868 10×
                expectedAssetOutput * (BPS_PRECISION - _getSlippageTolerance())
    869
            ) {
    870 13×
                revert MINIMUM_OUTPUT_AMOUNT_ASSETS_NOT_MET();
    871
            }
    872 24×
            emit FulfillHookExecuted(hook, vars.targetedYieldSource, hookCalldata);
    873
    874
            return vars.superVaultShares;
    875
        }
    876
    877
        /// @notice Process redeem fulfillments for multiple controllers
    878
        /// @param controllers Array of controller addresses
    879
        /// @param controllersLength Length of controllers array
    880
        /// @param processedShares Total shares processed
    881
        /// @param currentPPS Current price per share
    882
        function _processRedeemFulfillments(
    883
            address[] calldata controllers,
    884
            uint256 controllersLength,
    885
            uint256 processedShares,
    886
            uint256 currentPPS
    887
        ) internal {
    888 20×
            for (uint256 i; i < controllersLength; ++i) {
    889 42×
                SuperVaultState storage state = superVaultState[controllers[i]];
    890
    891
                uint256 crtControllerRequestedAmount = state.pendingRedeemRequest;
    892
                // Check for PPS slippage if there's a recorded request PPS and max slippage is set
    893 21×
                if (state.averageRequestPPS > 0 && _maxPPSSlippage > 0) {
    894
                    uint256 averageRequestPPS = state.averageRequestPPS;
    895
                    // Calculate the percentage decrease from request PPS to current PPS
    896
                    if (currentPPS < averageRequestPPS) {
    897 0
                        uint256 decrease = (
    898 0
                            (averageRequestPPS - currentPPS).mulDiv(
    899
                                BPS_PRECISION,
    900 0
                                averageRequestPPS,
    901
                                Math.Rounding.Floor
    902
                            )
    903
                        );
    904
                        // If decrease exceeds maximum allowed slippage, revert
    905 0
                        if (decrease > _maxPPSSlippage) revert SLIPPAGE_EXCEEDED();
    906
                    }
    907
                }
    908
    909
                uint256 currentAssets = _calculateHistoricalAssetsAndProcessFees(
    910
                    state,
    911
                    crtControllerRequestedAmount,
    912
                    currentPPS
    913
                );
    914
    915
                // Update user state, no partial redeems allowed
    916
                state.pendingRedeemRequest = 0;
    917 17×
                state.maxWithdraw += currentAssets;
    918
                state.averageRequestPPS = 0; // Reset PPS value after fulfillment
    919
    920
                // Call vault callback
    921
                _onRedeemClaimable(
    922 24×
                    controllers[i],
    923
                    currentAssets,
    924
                    crtControllerRequestedAmount,
    925
                    state.averageWithdrawPrice,
    926
                    state.accumulatorShares,
    927
                    state.accumulatorCostBasis
    928
                );
    929
            }
    930
        }
    931
    932
        /// @notice Calculate historical assets and process fees
    933
        /// @param state User's vault state
    934
        /// @param requestedShares Shares being redeemed
    935
        /// @param currentPricePerShare Current price per share
    936
        function _calculateHistoricalAssetsAndProcessFees(
    937
            SuperVaultState storage state,
    938
            uint256 requestedShares,
    939
            uint256 currentPricePerShare
    940
        ) private returns (uint256 currentAssets) {
    941
            // Calculate cost basis based on requested shares
    942
            uint256 historicalAssets = _calculateCostBasis(state, requestedShares);
    943
            uint256 currentAssetsWithFees;
    944
            // Process fees and get final assets
    945
            (currentAssetsWithFees, currentAssets) = _processFees(
    946
                requestedShares,
    947
                currentPricePerShare,
    948
                historicalAssets
    949
            );
    950
    951
            // Update average withdraw price if needed
    952
            if (requestedShares > 0) {
    953
                _updateAverageWithdrawPrice(
    954
                    state,
    955
                    requestedShares,
    956
                    currentAssetsWithFees
    957
                );
    958
            }
    959
    960
            return currentAssets;
    961
        }
    962
    963
        /// @notice Calculate cost basis for requested shares using weighted average approach
    964
        /// @param state User's vault state
    965
        /// @param requestedShares Shares being redeemed
    966
        function _calculateCostBasis(
    967
            SuperVaultState storage state,
    968
            uint256 requestedShares
    969
        ) private returns (uint256 costBasis) {
    970 10×
            if (requestedShares > state.accumulatorShares)
    971 0
                revert INSUFFICIENT_SHARES();
    972
    973
            // Calculate cost basis proportionally
    974
            costBasis = requestedShares.mulDiv(
    975
                state.accumulatorCostBasis,
    976
                state.accumulatorShares,
    977
                Math.Rounding.Floor
    978
            );
    979
    980
            // Update user's accumulator state
    981 21×
            state.accumulatorShares -= requestedShares;
    982 17×
            state.accumulatorCostBasis -= costBasis;
    983
    984
            return costBasis;
    985
        }
    986
    987
        // --- Fee Processing ---
    988
        /// @notice Calculate and transfer fees based on profit
    989
        /// @param requestedShares Shares being redeemed
    990
        /// @param currentPricePerShare Current price per share
    991
        /// @param historicalAssets Historical value of shares in assets
    992
        /// @return currentAssetsWithFees Current value of shares in assets (not net of fees)
    993
        /// @return currentAssets Current assets after fee deduction
    994
        function _processFees(
    995
            uint256 requestedShares,
    996
            uint256 currentPricePerShare,
    997
            uint256 historicalAssets
    998
        ) private returns (uint256 currentAssetsWithFees, uint256 currentAssets) {
    999
            // Calculate current value of the shares at current price
    1000
            currentAssetsWithFees = requestedShares.mulDiv(
    1001
                currentPricePerShare,
    1002
                PRECISION,
    1003
                Math.Rounding.Floor
    1004
            );
    1005
    1006
            // Apply fees only on profit
    1007
            currentAssets = _calculateAndTransferFee(
    1008
                currentAssetsWithFees,
    1009
                historicalAssets
    1010
            );
    1011
    1012
            // Ensure we don't exceed available balance
    1013
            uint256 balanceOfStrategy = _getTokenBalance(
    1014
                address(_asset),
    1015
                address(this)
    1016
            );
    1017 11×
            currentAssets = currentAssets > balanceOfStrategy
    1018
                ? balanceOfStrategy
    1019
                : currentAssets;
    1020
    1021
            return (currentAssetsWithFees, currentAssets);
    1022
        }
    1023
    1024
        /// @notice Calculate fee on profit and transfer to recipient
    1025
        /// @param currentAssetsWithFees Current value of shares in assets (not net of fees)
    1026
        /// @param historicalAssets Historical value of shares in assets
    1027
        /// @return currentAssets Current assets after fee deduction
    1028
        function _calculateAndTransferFee(
    1029
            uint256 currentAssetsWithFees,
    1030
            uint256 historicalAssets
    1031
        ) private returns (uint256 currentAssets) {
    1032
            currentAssets = currentAssetsWithFees;
    1033
            if (currentAssetsWithFees > historicalAssets) {
    1034 0
                uint256 profit = currentAssetsWithFees - historicalAssets;
    1035 0
                uint256 performanceFeeBps = feeConfig.performanceFeeBps;
    1036 0
                uint256 totalFee = profit.mulDiv(
    1037
                    performanceFeeBps,
    1038
                    BPS_PRECISION,
    1039
                    Math.Rounding.Floor
    1040
                );
    1041 0
                if (totalFee > 0) {
    1042
                    // Calculate Superform's portion of the fee using revenueShare from SuperGovernor
    1043 0
                    uint256 superformFee = totalFee.mulDiv(
    1044 0
                        superGovernor.getFee(FeeType.SUPER_VAULT_PERFORMANCE_FEE),
    1045
                        BPS_PRECISION,
    1046 0
                        Math.Rounding.Floor
    1047
                    );
    1048 0
                    uint256 recipientFee = totalFee - superformFee;
    1049
    1050
                    // Transfer fees
    1051 0
                    if (superformFee > 0) {
    1052
                        // Get treasury address from SuperGovernor
    1053 0
                        address treasury = superGovernor.getAddress(
    1054 0
                            superGovernor.TREASURY()
    1055
                        );
    1056 0
                        _safeTokenTransfer(address(_asset), treasury, superformFee);
    1057 0
                        emit FeePaid(treasury, superformFee, performanceFeeBps);
    1058
                    }
    1059
    1060 0
                    if (recipientFee > 0) {
    1061 0
                        address recipient = feeConfig.recipient;
    1062 0
                        if (recipient == address(0)) revert ZERO_ADDRESS();
    1063 0
                        _safeTokenTransfer(
    1064 0
                            address(_asset),
    1065 0
                            recipient,
    1066 0
                            recipientFee
    1067
                        );
    1068 0
                        emit FeePaid(recipient, recipientFee, performanceFeeBps);
    1069
                    }
    1070
    1071 0
                    currentAssets -= totalFee;
    1072
                }
    1073
            }
    1074
            return currentAssets;
    1075
        }
    1076
    1077
        /// @notice Internal function to update the average withdraw price
    1078
        /// @param state Storage reference to the vault state
    1079
        /// @param requestedShares Number of shares requested
    1080
        /// @param currentAssetsWithFees Current assets with fees
    1081
        function _updateAverageWithdrawPrice(
    1082
            SuperVaultState storage state,
    1083
            uint256 requestedShares,
    1084
            uint256 currentAssetsWithFees
    1085
        ) private {
    1086
            uint256 existingShares;
    1087
            uint256 existingAssets;
    1088
    1089 15×
            if (state.maxWithdraw > 0 && state.averageWithdrawPrice > 0) {
    1090 0
                existingShares = state.maxWithdraw.mulDiv(
    1091 0
                    PRECISION,
    1092 0
                    state.averageWithdrawPrice,
    1093 0
                    Math.Rounding.Floor
    1094
                );
    1095 0
                existingAssets = state.maxWithdraw;
    1096
            }
    1097
    1098
            uint256 newTotalShares = existingShares + requestedShares;
    1099
            uint256 newTotalAssets = existingAssets + currentAssetsWithFees;
    1100
    1101
            if (newTotalShares > 0) {
    1102 11×
                state.averageWithdrawPrice = newTotalAssets.mulDiv(
    1103
                    PRECISION,
    1104
                    newTotalShares,
    1105
                    Math.Rounding.Floor
    1106
                );
    1107
            }
    1108
        }
    1109
    1110
        /// @notice Internal function to get the SuperVaultAggregator
    1111
        /// @return The SuperVaultAggregator
    1112
        function _getSuperVaultAggregator()
    1113
            internal
    1114
            view
    1115
            returns (ISuperVaultAggregator)
    1116
        {
    1117 244×
            address aggregatorAddress = superGovernor.getAddress(
    1118 232×
                superGovernor.SUPER_VAULT_AGGREGATOR()
    1119
            );
    1120
    1121
            return ISuperVaultAggregator(aggregatorAddress);
    1122
        }
    1123
    1124
        /// @notice Internal function to check if a manager is authorized
    1125
        /// @param manager_ The manager to check
    1126
        function _isManager(address manager_) internal view {
    1127 132×
            if (!_getSuperVaultAggregator().isAnyManager(manager_, address(this))) {
    1128 26×
                revert MANAGER_NOT_AUTHORIZED();
    1129
            }
    1130
        }
    1131
    1132
        /// @notice Internal function to check if a manager is the primary manager
    1133
        /// @param manager_ The manager to check
    1134
        function _isPrimaryManager(address manager_) internal view {
    1135
            if (
    1136 20×
                !_getSuperVaultAggregator().isMainManager(manager_, address(this))
    1137
            ) {
    1138
                revert MANAGER_NOT_AUTHORIZED();
    1139
            }
    1140
        }
    1141
    1142
        /// @notice Internal function to manage a yield source
    1143
        /// @param source Address of the yield source
    1144
        /// @param oracle Address of the oracle
    1145
        /// @param actionType Type of action: 0=Add, 1=UpdateOracle, 2=Remove
    1146
        function _manageYieldSource(
    1147
            address source,
    1148
            address oracle,
    1149
            uint8 actionType
    1150
        ) internal {
    1151
            if (actionType == 0) {
    1152
                _addYieldSource(source, oracle);
    1153
            } else if (actionType == 1) {
    1154
                _updateYieldSourceOracle(source, oracle);
    1155
            } else if (actionType == 2) {
    1156
                _removeYieldSource(source);
    1157
            } else {
    1158
                revert ACTION_TYPE_DISALLOWED();
    1159
            }
    1160
        }
    1161
    1162
        /// @notice Internal function to add a yield source
    1163
        /// @param source Address of the yield source
    1164
        /// @param oracle Address of the oracle
    1165
        function _addYieldSource(address source, address oracle) internal {
    1166 27×
            if (source == address(0) || oracle == address(0)) revert ZERO_ADDRESS();
    1167 19×
            if (yieldSources[source] != address(0))
    1168 13×
                revert YIELD_SOURCE_ALREADY_EXISTS();
    1169 26×
            yieldSources[source] = oracle;
    1170
            if (!yieldSourcesList.add(source)) revert YIELD_SOURCE_ALREADY_EXISTS();
    1171
    1172 14×
            emit YieldSourceAdded(source, oracle);
    1173
        }
    1174
    1175
        /// @notice Internal function to update a yield source's oracle
    1176
        /// @param source Address of the yield source
    1177
        /// @param oracle Address of the oracle
    1178
        function _updateYieldSourceOracle(address source, address oracle) internal {
    1179 18×
            if (oracle == address(0)) revert ZERO_ADDRESS();
    1180 16×
            address oldOracle = yieldSources[source];
    1181 16×
            if (oldOracle == address(0)) revert YIELD_SOURCE_NOT_FOUND();
    1182 29×
            yieldSources[source] = oracle;
    1183
    1184
            emit YieldSourceOracleUpdated(source, oldOracle, oracle);
    1185
        }
    1186
    1187
        /// @notice Internal function to remove a yield source
    1188
        /// @param source Address of the yield source
    1189
        function _removeYieldSource(address source) internal {
    1190 31×
            if (yieldSources[source] == address(0)) revert YIELD_SOURCE_NOT_FOUND();
    1191
    1192
            // Remove from mapping
    1193 18×
            delete yieldSources[source];
    1194
    1195
            // Remove from EnumerableSet
    1196
            if (!yieldSourcesList.remove(source)) revert YIELD_SOURCE_NOT_FOUND();
    1197
    1198 10×
            emit YieldSourceRemoved(source);
    1199
        }
    1200
    1201
        /// @notice Internal function to propose an emergency withdraw
    1202
        function _proposeEmergencyWithdraw() internal {
    1203
            _isPrimaryManager(msg.sender);
    1204
    1205
            proposedEmergencyWithdrawable = true;
    1206
            emergencyWithdrawableEffectiveTime = block.timestamp + ONE_WEEK;
    1207 13×
            emit EmergencyWithdrawableProposed(
    1208
                true,
    1209
                emergencyWithdrawableEffectiveTime
    1210
            );
    1211
        }
    1212
    1213
        /// @notice Internal function to execute an emergency withdraw
    1214
        function _executeEmergencyWithdrawActivation() internal {
    1215 20×
            if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL();
    1216
            if (block.timestamp < emergencyWithdrawableEffectiveTime)
    1217 13×
                revert INVALID_TIMESTAMP();
    1218 13×
            emergencyWithdrawable = proposedEmergencyWithdrawable;
    1219 11×
            proposedEmergencyWithdrawable = false;
    1220
            emergencyWithdrawableEffectiveTime = 0;
    1221
            emit EmergencyWithdrawableUpdated(emergencyWithdrawable);
    1222
        }
    1223
    1224
        /// @notice Internal function to cancel an emergency withdraw proposal
    1225
        function _cancelEmergencyWithdrawProposal() internal {
    1226
            _isPrimaryManager(msg.sender);
    1227
    1228 20×
            if (emergencyWithdrawableEffectiveTime == 0) revert NO_PROPOSAL();
    1229
            proposedEmergencyWithdrawable = false;
    1230
            emergencyWithdrawableEffectiveTime = 0;
    1231
            emit EmergencyWithdrawableProposalCanceled();
    1232
        }
    1233
    1234
        /// @notice Internal function to perform an emergency withdraw
    1235
        /// @param recipient Address to receive the assets
    1236
        /// @param amount Amount of assets to withdraw
    1237
        function _performEmergencyWithdraw(
    1238
            address recipient,
    1239
            uint256 amount
    1240
        ) internal {
    1241
            _isPrimaryManager(msg.sender);
    1242
    1243 20×
            if (!emergencyWithdrawable) revert INVALID_EMERGENCY_WITHDRAWAL();
    1244
            if (recipient == address(0)) revert ZERO_ADDRESS();
    1245 12×
            uint256 freeAssets = _getTokenBalance(address(_asset), address(this));
    1246 26×
            if (amount == 0 || amount > freeAssets) revert INSUFFICIENT_FUNDS();
    1247 0
            _safeTokenTransfer(address(_asset), recipient, amount);
    1248 0
            emit EmergencyWithdrawal(recipient, amount);
    1249
        }
    1250
    1251
        /// @notice Internal function to check if a hook is a fulfill requests hook
    1252
        /// @param hook Address of the hook
    1253
        /// @return True if the hook is a fulfill requests hook, false otherwise
    1254
        function _isFulfillRequestsHook(address hook) private view returns (bool) {
    1255 116×
            return superGovernor.isFulfillRequestsHookRegistered(hook);
    1256
        }
    1257
    1258
        /// @notice Internal function to check if a hook is registered
    1259
        /// @param hook Address of the hook
    1260
        /// @return True if the hook is registered, false otherwise
    1261
        function _isRegisteredHook(address hook) private view returns (bool) {
    1262 30×
            return superGovernor.isHookRegistered(hook);
    1263
        }
    1264
    1265
        /// @notice Internal function to decode a hook's use previous hook amount
    1266
        /// @param hook Address of the hook
    1267
        /// @param hookCalldata Call data for the hook
    1268
        /// @return True if the hook should use the previous hook amount, false otherwise
    1269
        function _decodeHookUsePrevHookAmount(
    1270
            address hook,
    1271
            bytes memory hookCalldata
    1272
        ) private pure returns (bool) {
    1273
            try
    1274 120×
                ISuperHookContextAware(hook).decodeUsePrevHookAmount(hookCalldata)
    1275
            returns (bool usePrevHookAmount) {
    1276
                return usePrevHookAmount;
    1277
            } catch {
    1278
                return false;
    1279
            }
    1280
        }
    1281
    1282
        /// @notice Internal function to get the previous hook's output amount
    1283
        /// @param prevHook Address of the previous hook
    1284
        /// @return Output amount of the previous hook
    1285 0
        function _getPreviousHookOutAmount(
    1286
            address prevHook
    1287 0
        ) private view returns (uint256) {
    1288 0
            return ISuperHookResultOutflow(prevHook).getOutAmount(address(this));
    1289
        }
    1290
    1291
        /// @notice Internal function to handle a redeem claimable
    1292
        /// @param controller Address of the controller
    1293
        /// @param assetsFulfilled Amount of assets fulfilled
    1294
        /// @param sharesFulfilled Amount of shares fulfilled
    1295
        /// @param averageWithdrawPrice Average withdraw price
    1296
        /// @param accumulatorShares Accumulator shares
    1297
        /// @param accumulatorCostBasis Accumulator cost basis
    1298
        function _onRedeemClaimable(
    1299
            address controller,
    1300
            uint256 assetsFulfilled,
    1301
            uint256 sharesFulfilled,
    1302
            uint256 averageWithdrawPrice,
    1303
            uint256 accumulatorShares,
    1304
            uint256 accumulatorCostBasis
    1305
        ) private {
    1306 45×
            ISuperVault(_vault).onRedeemClaimable(
    1307
                controller,
    1308
                assetsFulfilled,
    1309
                sharesFulfilled,
    1310
                averageWithdrawPrice,
    1311
                accumulatorShares,
    1312
                accumulatorCostBasis
    1313
            );
    1314
        }
    1315
    1316
        /// @notice Internal function to handle a redeem
    1317
        /// @param controller Address of the controller
    1318
        /// @param shares Amount of shares
    1319
        function _handleRequestRedeem(address controller, uint256 shares) private {
    1320
            if (shares == 0) revert INVALID_AMOUNT();
    1321
            if (controller == address(0)) revert ZERO_ADDRESS();
    1322 14×
            SuperVaultState storage state = superVaultState[controller];
    1323
    1324
            // Defense-in-depth: assert controller has accumulator shares
    1325
            if (state.accumulatorShares == 0) revert INSUFFICIENT_SHARES();
    1326
    1327
            // Get current PPS from aggregator to use as baseline for slippage protection
    1328
            uint256 currentPPS = getStoredPPS();
    1329 19×
            if (currentPPS == 0) revert INVALID_PPS();
    1330
    1331
            // Calculate weighted average of PPS if there's an existing request
    1332
            if (state.pendingRedeemRequest > 0) {
    1333
                // Calculate weighted average of PPS based on share amounts
    1334
                uint256 existingSharesInRequest = state.pendingRedeemRequest;
    1335
                uint256 newTotalSharesInRequest = existingSharesInRequest + shares;
    1336
    1337
                // Use weighted average formula: (existingShares * existingPPS + newShares * currentPPS) / totalShares
    1338
                state.averageRequestPPS =
    1339 22×
                    ((existingSharesInRequest * state.averageRequestPPS) +
    1340
                        (shares * currentPPS)) /
    1341
                    newTotalSharesInRequest;
    1342
    1343
                // Update total shares
    1344
                state.pendingRedeemRequest = newTotalSharesInRequest;
    1345
            } else {
    1346
                // First request for this controller
    1347
                state.pendingRedeemRequest = shares;
    1348
                state.averageRequestPPS = currentPPS;
    1349
            }
    1350
    1351 16×
            emit RedeemRequestPlaced(controller, controller, shares);
    1352
        }
    1353
    1354
        /// @notice Internal function to handle a redeem cancellation
    1355
        /// @param controller Address of the controller
    1356
        function _handleCancelRedeem(address controller) private {
    1357
            if (controller == address(0)) revert ZERO_ADDRESS();
    1358 14×
            SuperVaultState storage state = superVaultState[controller];
    1359
            uint256 pendingShares = state.pendingRedeemRequest;
    1360 19×
            if (pendingShares == 0) revert REQUEST_NOT_FOUND();
    1361
            // Only clear pending request metadata
    1362
            state.pendingRedeemRequest = 0;
    1363
            state.averageRequestPPS = 0;
    1364
            emit RedeemRequestCanceled(controller, pendingShares);
    1365
        }
    1366
    1367
        /// @notice Internal function to handle a redeem claim
    1368
        /// @param controller Address of the controller
    1369
        /// @param receiver Address of the receiver
    1370
        /// @param assetsToClaim Amount of assets to claim
    1371
        function _handleClaimRedeem(
    1372
            address controller,
    1373
            address receiver,
    1374
            uint256 assetsToClaim
    1375
        ) private {
    1376
            if (assetsToClaim == 0) revert INVALID_AMOUNT();
    1377
            if (controller == address(0)) revert ZERO_ADDRESS();
    1378 17×
            SuperVaultState storage state = superVaultState[controller];
    1379
    1380
            // Handle dust collection for rounding errors
    1381
            uint256 actualAmountToClaim = assetsToClaim;
    1382 60×
            uint256 remainingAssets = _asset.balanceOf(address(this));
    1383
    1384
            // If user is requesting slightly more than available due to rounding errors,
    1385
            // and the difference is small (dust), give them the remaining balance
    1386
            if (
    1387
                assetsToClaim > remainingAssets &&
    1388 0
                assetsToClaim - remainingAssets <= TOLERANCE_CONSTANT
    1389
            ) {
    1390 0
                actualAmountToClaim = remainingAssets;
    1391
            }
    1392
    1393 10×
            if (state.maxWithdraw < actualAmountToClaim)
    1394 0
                revert INVALID_REDEEM_CLAIM();
    1395 17×
            state.maxWithdraw -= actualAmountToClaim;
    1396 10×
            _asset.safeTransfer(receiver, actualAmountToClaim);
    1397 20×
            emit RedeemRequestFulfilled(
    1398
                receiver,
    1399
                controller,
    1400
                actualAmountToClaim,
    1401
                0
    1402
            );
    1403
        }
    1404
    1405
        /// @notice Internal function to safely transfer tokens
    1406
        /// @param token Address of the token
    1407
        /// @param recipient Address to receive the tokens
    1408
        /// @param amount Amount of tokens to transfer
    1409
        function _safeTokenTransfer(
    1410
            address token,
    1411
            address recipient,
    1412
            uint256 amount
    1413
        ) private {
    1414 22×
            if (amount > 0) IERC20(token).safeTransfer(recipient, amount);
    1415
        }
    1416
    1417
        /// @notice Internal function to get the token balance of an account
    1418
        /// @param token Address of the token
    1419
        /// @param account Address of the account
    1420
        /// @return Token balance of the account
    1421
        function _getTokenBalance(
    1422
            address token,
    1423
            address account
    1424
        ) private view returns (uint256) {
    1425 112×
            return IERC20(token).balanceOf(account);
    1426
        }
    1427
    1428
        /// @notice Internal function to get the slippage tolerance
    1429
        /// @return Slippage tolerance
    1430
        function _getSlippageTolerance() private pure returns (uint256) {
    1431
            return SV_SLIPPAGE_TOLERANCE_BPS;
    1432
        }
    1433
    1434
        /// @notice Internal function to check if the caller is the vault
    1435
        /// @dev This is used to prevent unauthorized access to certain functions
    1436
        function _requireVault() internal view {
    1437 45×
            if (msg.sender != _vault) revert ACCESS_DENIED();
    1438
        }
    1439
    1440
        /// @notice Checks if the strategy is currently paused
    1441
        /// @dev This calls SuperVaultAggregator.isStrategyPaused to determine pause status
    1442
        /// @return True if the strategy is paused, false otherwise
    1443
        function _isPaused() internal view returns (bool) {
    1444 248×
            return _getSuperVaultAggregator().isStrategyPaused(address(this));
    1445
        }
    1446
    1447
        /// @notice Validates a hook using the Merkle root system
    1448
        /// @param hook Address of the hook to validate
    1449
        /// @param hookCalldata Calldata to be passed to the hook
    1450
        /// @param globalProof Merkle proof for the global root
    1451
        /// @param strategyProof Merkle proof for the strategy-specific root
    1452
        /// @return isValid True if the hook is valid, false otherwise
    1453 10×
        function _validateHook(
    1454
            address hook,
    1455
            bytes memory hookCalldata,
    1456
            bytes32[] memory globalProof,
    1457
            bytes32[] memory strategyProof
    1458
        ) internal view returns (bool) {
    1459
            return
    1460 138×
                _getSuperVaultAggregator().validateHook(
    1461
                    address(this),
    1462 48×
                    ISuperVaultAggregator.ValidateHookArgs({
    1463
                        hookAddress: hook,
    1464 132×
                        hookArgs: ISuperHookInspector(hook).inspect(hookCalldata),
    1465
                        globalProof: globalProof,
    1466
                        strategyProof: strategyProof
    1467
                    })
    1468
                );
    1469
        }
    1470
    }
    0% src/UP/Up.sol
    Lines covered: 0 / 22 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    5
    import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
    6
    import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
    7
    import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
    8
    9
    /**
    10
     * @title Up
    11
     * @author Superform Foundation
    12
     */
    13 0
    contract Up is ERC20, ERC20Permit, Ownable2Step {
    14 0
        uint256 public constant INITIAL_SUPPLY = 1_000_000_000 * 10 ** 18;
    15 0
        uint256 public constant MINT_CAP_BPS = 200; // 2%
    16 0
        uint256 public constant DAYS_PER_YEAR = 365 days;
    17 0
        uint256 public constant INITIAL_MINT_LOCK = 3 * 365 days; // 3 years
    18
    19 0
        uint256 public lastMintTimestamp;
    20 0
        uint256 public immutable INITIAL_MINT_TIMESTAMP;
    21
    22
        event TokensMinted(address indexed to, uint256 amount);
    23
    24
        error MintingTooEarly();
    25
        error MintAmountTooHigh();
    26
        error InitialLockPeriodNotOver();
    27
    28 0
        constructor(address initialOwner) ERC20("Superform", "UP") ERC20Permit("Superform") Ownable(initialOwner) {
    29 0
            _mint(initialOwner, INITIAL_SUPPLY);
    30 0
            lastMintTimestamp = block.timestamp;
    31 0
            INITIAL_MINT_TIMESTAMP = block.timestamp;
    32
        }
    33
    34
        /**
    35
         * @dev Allows owner to mint new tokens once per year after 3 years, up to 2% of total supply
    36
         * @param to Address to mint tokens to
    37
         * @param amount Amount of tokens to mint
    38
         */
    39 0
        function mint(address to, uint256 amount) external onlyOwner {
    40
            // Check if 3 years have passed since initial mint
    41 0
            if (block.timestamp < INITIAL_MINT_TIMESTAMP + INITIAL_MINT_LOCK) {
    42 0
                revert InitialLockPeriodNotOver();
    43
            }
    44
    45
            // Check if enough time has passed since last mint
    46 0
            if (block.timestamp < lastMintTimestamp + DAYS_PER_YEAR) {
    47 0
                revert MintingTooEarly();
    48
            }
    49
    50
            // Calculate maximum mint amount (2% of total supply)
    51 0
            uint256 maxMintAmount = (totalSupply() * MINT_CAP_BPS) / 10_000;
    52 0
            if (amount > maxMintAmount) {
    53 0
                revert MintAmountTooHigh();
    54
            }
    55
    56 0
            lastMintTimestamp = block.timestamp;
    57 0
            _mint(to, amount);
    58 0
            emit TokensMinted(to, amount);
    59
        }
    60
    }
    0% src/UP/UpDistributor.sol
    Lines covered: 0 / 30 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
    5
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    6
    import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
    7
    import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
    8
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    9
    import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
    10
    11
    /**
    12
     * @title UpDistributor
    13
     * @notice A contract for distributing tokens using a merkle tree for verification
    14
     * @dev The foundation can reclaim unclaimed tokens
    15
     */
    16 0
    contract UpDistributor is Ownable2Step {
    17
        using SafeERC20 for IERC20;
    18
    19 0
        IERC20 public immutable token;
    20 0
        bytes32 public merkleRoot;
    21
    22
        /// @notice Track which addresses have claimed their tokens
    23 0
        mapping(address => bool) public hasClaimed;
    24
    25
        event MerkleRootSet(bytes32 merkleRoot);
    26
        event TokensClaimed(address indexed user, uint256 amount);
    27
        event TokensReclaimed(uint256 amount);
    28
    29
        error ALREADY_CLAIMED();
    30
        error NO_TOKENS_TO_RECLAIM();
    31
        error INVALID_MERKLE_PROOF();
    32
        error INVALID_TOKEN_ADDRESS();
    33
    34 0
        constructor(address _token, address initialOwner) Ownable(initialOwner) {
    35 0
            if (_token == address(0)) revert INVALID_TOKEN_ADDRESS();
    36 0
            token = IERC20(_token);
    37
        }
    38
    39
        /**
    40
         * @notice Set a new merkle root for the distribution
    41
         * @param _merkleRoot The new merkle root
    42
         */
    43 0
        function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
    44 0
            merkleRoot = _merkleRoot;
    45 0
            emit MerkleRootSet(_merkleRoot);
    46
        }
    47
    48
        /**
    49
         * @notice Claim tokens if you are part of the merkle tree
    50
         * @param amount The amount of tokens to claim
    51
         * @param merkleProof A proof of inclusion in the merkle tree
    52
         */
    53 0
        function claim(uint256 amount, bytes32[] calldata merkleProof) external {
    54
            // Verify user hasn't already claimed
    55 0
            if (hasClaimed[msg.sender]) revert ALREADY_CLAIMED();
    56
    57
            // Verify the merkle proof
    58 0
            bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amount))));
    59 0
            if (!MerkleProof.verify(merkleProof, merkleRoot, leaf)) revert INVALID_MERKLE_PROOF();
    60
    61
            // Mark as claimed and transfer tokens
    62 0
            hasClaimed[msg.sender] = true;
    63 0
            emit TokensClaimed(msg.sender, amount);
    64 0
            token.safeTransfer(msg.sender, amount);
    65
        }
    66
    67
        /**
    68
         * @notice Claim tokens for a recipient
    69
         * @dev This function can be called by users with smart accounts
    70
         * @param recipient The address to claim tokens for
    71
         * @param amount The amount of tokens to claim
    72
         * @param merkleProof A proof of inclusion in the merkle tree
    73
         */
    74 0
        function claimOnBehalf(
    75
            address recipient,
    76
            uint256 amount,
    77
            bytes32[] calldata merkleProof
    78
        )
    79
            external
    80 0
        {
    81
            // Verify user hasn't already claimed
    82 0
            if (hasClaimed[recipient]) revert ALREADY_CLAIMED();
    83
    84
            // Verify the merkle proof
    85 0
            bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(recipient, amount))));
    86 0
            if (!MerkleProof.verify(merkleProof, merkleRoot, leaf)) revert INVALID_MERKLE_PROOF();
    87
    88
            // Mark as claimed and transfer tokens
    89 0
            hasClaimed[recipient] = true;
    90 0
            emit TokensClaimed(recipient, amount);
    91 0
            token.safeTransfer(recipient, amount);
    92
        }
    93
    94
        /**
    95
         * @notice Allow the foundation to reclaim unclaimed tokens
    96
         * @param amount The amount of tokens to reclaim
    97
         */
    98 0
        function reclaimTokens(uint256 amount) external onlyOwner {
    99 0
            uint256 balance = token.balanceOf(address(this));
    100 0
            if (amount > balance) revert NO_TOKENS_TO_RECLAIM();
    101
    102 0
            emit TokensReclaimed(amount);
    103 0
            token.safeTransfer(owner(), amount);
    104
        }
    105
    }
    0% src/VaultBank/VaultBank.sol
    Lines covered: 0 / 104 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { BytesLib } from "../vendor/BytesLib.sol";
    6
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    7
    import { ICrossL2ProverV2 } from "../vendor/polymer/ICrossL2ProverV2.sol";
    8
    import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
    9
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    10
    11
    // Superform
    12
    import { VaultBankSuperPosition } from "./VaultBankSuperPosition.sol";
    13
    import { IVaultBank, IVaultBankSource } from "../interfaces/VaultBank/IVaultBank.sol";
    14
    import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
    15
    import { VaultBankDestination } from "./VaultBankDestination.sol";
    16
    import { VaultBankSource } from "./VaultBankSource.sol";
    17
    import { Bank } from "../Bank.sol";
    18
    19
    /// @title VaultBank
    20
    /// @author Superform Labs
    21
    /// @notice Locks assets and mints SuperPositions
    22 0
    contract VaultBank is IVaultBank, VaultBankSource, VaultBankDestination, Bank {
    23
        using SafeERC20 for IERC20;
    24
        using BytesLib for bytes;
    25
    26
        /*//////////////////////////////////////////////////////////////
    27
                                     STORAGE
    28
        //////////////////////////////////////////////////////////////*/
    29 0
        ISuperGovernor public immutable SUPER_GOVERNOR;
    30
    31 0
        mapping(uint64 toChainId => uint256 nonce) public nonces;
    32 0
        mapping(uint64 fromChainId => mapping(uint256 nonce => bool isUsed)) public noncesUsed;
    33
    34 0
        constructor(address governor_) {
    35 0
            if (governor_ == address(0)) revert INVALID_VALUE();
    36 0
            SUPER_GOVERNOR = ISuperGovernor(governor_);
    37
        }
    38
    39
        modifier onlyRelayer() {
    40 0
            if (!SUPER_GOVERNOR.isRelayer(msg.sender)) revert INVALID_RELAYER();
    41
            _;
    42
        }
    43
    44
        modifier onlyBankManager() {
    45 0
            if (!IAccessControl(address(SUPER_GOVERNOR)).hasRole(SUPER_GOVERNOR.BANK_MANAGER_ROLE(), msg.sender)) {
    46 0
                revert INVALID_BANK_MANAGER();
    47
            }
    48
            _;
    49
        }
    50
    51
        /// @dev to receive ETH rewards
    52
        receive() external payable { }
    53
    54
        /*//////////////////////////////////////////////////////////////
    55
                                     EXTERNAL METHODS
    56
        //////////////////////////////////////////////////////////////*/
    57
        // ------------------ SOURCE VAULTBANK METHODS ------------------
    58
        /// @inheritdoc IVaultBank
    59 0
        function lockAsset(
    60
            bytes32 yieldSourceOracleId,
    61
            address account,
    62
            address token,
    63
            address hookAddress,
    64
            uint256 amount,
    65
            uint64 toChainId
    66
        )
    67
            external
    68 0
        {
    69 0
            address vaultBank = SUPER_GOVERNOR.getVaultBank(toChainId);
    70
    71 0
            if (vaultBank == address(0)) revert INVALID_VAULT_BANK_ADDRESS();
    72 0
            if (!SUPER_GOVERNOR.isHookRegistered(hookAddress)) revert INVALID_HOOK();
    73
    74 0
            uint256 _nonce = nonces[toChainId];
    75 0
            nonces[toChainId]++;
    76 0
            _lockAssetForChain(yieldSourceOracleId, account, token, amount, toChainId, _nonce);
    77
        }
    78
    79
        /// @inheritdoc IVaultBank
    80 0
        function unlockAsset(
    81
            address account,
    82
            address token,
    83
            uint256 amount,
    84
            uint64 fromChainId,
    85
            bytes32 yieldSourceOracleId,
    86
            bytes calldata proof
    87
        )
    88
            external
    89
        {
    90
            // validate and mark `proof.nonce[fromChainId]` as used
    91 0
            _validateUnlockAssetProof(account, yieldSourceOracleId, token, amount, fromChainId, proof);
    92
    93
            //`toChainId` is current chain
    94 0
            uint256 _nonce = nonces[uint64(_chainId)];
    95 0
            nonces[uint64(_chainId)]++;
    96 0
            _releaseAssetFromChain(yieldSourceOracleId, account, token, amount, fromChainId, _nonce);
    97
        }
    98
    99
        /// @inheritdoc IVaultBank
    100 0
        function executeHooks(IVaultBank.HookExecutionData calldata executionData) external onlyBankManager {
    101 0
            _executeHooks(executionData);
    102
        }
    103
    104
        /// @inheritdoc IVaultBank
    105 0
        function batchDistributeRewardsToSuperBank(
    106
            address[] memory rewards,
    107
            uint256[] memory amounts
    108
        )
    109
            external
    110
            onlyRelayer
    111 0
        {
    112 0
            uint256 len = rewards.length;
    113 0
            for (uint256 i; i < len; ++i) {
    114 0
                _distributeRewardsToSuperBank(rewards[i], amounts[i]);
    115
            }
    116 0
            emit BatchDistributeRewardsToSuperBank(rewards, amounts);
    117
        }
    118
    119
        // ------------------ DESTINATION VAULTBANK METHODS ------------------
    120
        /// @inheritdoc IVaultBank
    121 0
        function distributeSuperPosition(
    122
            address account_,
    123
            uint256 amount_,
    124
            SourceAssetInfo calldata sourceAsset_,
    125
            bytes calldata proof_
    126
        )
    127
            external
    128
            override
    129
            onlyRelayer
    130 0
        {
    131
            // validate and mark `proof.nonce[sourceAsset_.chainId]` as used
    132 0
            _validateDistributeSPProof(account_, sourceAsset_.yieldSourceOracleId, sourceAsset_.asset, amount_, sourceAsset_.chainId, proof_);
    133
    134 0
            address spAddress = _retrieveSuperPosition(
    135 0
                sourceAsset_.yieldSourceOracleId,
    136 0
                sourceAsset_.chainId,
    137 0
                sourceAsset_.asset,
    138 0
                sourceAsset_.name,
    139 0
                sourceAsset_.symbol,
    140 0
                sourceAsset_.decimals
    141
            );
    142 0
            _mintSP(account_, spAddress, amount_);
    143
    144 0
            nonces[uint64(_chainId)]++;
    145
    146 0
            emit SuperpositionsMinted(
    147 0
                account_, spAddress, sourceAsset_.asset, amount_, sourceAsset_.chainId, _extractNonce(proof_)
    148
            );
    149
        }
    150
    151
        /// @inheritdoc IVaultBank
    152 0
        function burnSuperPosition(
    153
            uint256 amount_,
    154
            address spAddress_,
    155
            uint64 forChainId_,
    156
            bytes32 yieldSourceOracleId_
    157
        )
    158
            external
    159
            override
    160 0
        {
    161 0
            _burnSP(msg.sender, spAddress_, amount_);
    162 0
            uint256 _nonce = nonces[forChainId_];
    163 0
            nonces[forChainId_]++;
    164 0
            emit SuperpositionsBurned(
    165 0
                msg.sender,
    166
                spAddress_,
    167 0
                _spAssetsInfo[spAddress_].spToToken[forChainId_][yieldSourceOracleId_],
    168
                amount_,
    169
                forChainId_,
    170
                _nonce
    171
            );
    172
        }
    173
    174 0
        function transferSuperPositionOwnership(address superPos, address newOwner) external onlyBankManager {
    175 0
            VaultBankSuperPosition(superPos).transferOwnership(newOwner);
    176
        }
    177
    178
        /*//////////////////////////////////////////////////////////////
    179
                              INTERNAL METHODS
    180
        //////////////////////////////////////////////////////////////*/
    181 0
        function _getMerkleRootForHook(address hookAddress) internal view override returns (bytes32) {
    182 0
            return SUPER_GOVERNOR.getVaultBankHookMerkleRoot(hookAddress);
    183
        }
    184
    185 0
        function _distributeRewardsToSuperBank(address token, uint256 amount) internal {
    186
            // get SuperBank address
    187 0
            address superBank = SUPER_GOVERNOR.getAddress(SUPER_GOVERNOR.SUPER_BANK());
    188
    189 0
            if (token == address(0)) {
    190
                // distribute ETH
    191 0
                (bool success,) = superBank.call{ value: amount }("");
    192 0
                if (!success) revert INVALID_VALUE();
    193
            } else {
    194
                // distribute ERC20
    195 0
                IERC20(token).safeTransfer(superBank, amount);
    196
            }
    197
        }
    198
    199 0
        function _validateDistributeSPProof(
    200
            address account,
    201
            bytes32 yieldSourceOracleId,
    202
            address token,
    203
            uint256 amount,
    204
            uint64 fromChainId,
    205
            bytes calldata proof
    206
        )
    207
            internal
    208
        {
    209 0
            (uint32 chainId, address emittingContract, bytes memory topics, bytes memory unindexedData) =
    210 0
                ICrossL2ProverV2(SUPER_GOVERNOR.getProver()).validateEvent(proof);
    211
    212 0
            address vaultBank = SUPER_GOVERNOR.getVaultBank(uint64(fromChainId));
    213
    214 0
            if (emittingContract != vaultBank) revert INVALID_PROOF_EMITTER();
    215
    216 0
            _validateSPTopics(account, yieldSourceOracleId, token, topics);
    217 0
            _validateSPData(amount, fromChainId, chainId, unindexedData);
    218
        }
    219
    220 0
        function _extractNonce(bytes calldata proof_) internal view returns (uint256) {
    221 0
            (,,, bytes memory unindexedData) = ICrossL2ProverV2(SUPER_GOVERNOR.getProver()).validateEvent(proof_);
    222
    223 0
            (,,, uint256 eventNonce) = abi.decode(unindexedData, (uint256, uint64, uint64, uint256));
    224
            return eventNonce;
    225
        }
    226
    227 0
        function _validateSPTopics(address account, bytes32 yieldSourceOracleId, address token, bytes memory topics) private pure {
    228
             // topics[0] = event signature
    229 0
            if (topics.toBytes32(0) !=  IVaultBankSource.SharesLocked.selector) revert INVALID_PROOF_EVENT();
    230
            
    231
            // topics[1] = yieldSourceOracleId
    232
            if (topics.toBytes32(32) != yieldSourceOracleId) revert INVALID_PROOF_YIELD_SOURCE_ORACLE_ID();
    233
    234
            // topics[2] = account
    235
            if (topics.toBytes32(64) != bytes32(uint256(uint160(account)))) revert INVALID_PROOF_ACCOUNT();
    236
    237
            // topics[3] = srcTokenAddress 
    238
            if (topics.toBytes32(96) != keccak256(abi.encodePacked(token))) revert INVALID_PROOF_TOKEN();
    239
        }
    240
    241 0
        function _validateSPData(uint256 amount, uint64 fromChainId, uint32 chainId, bytes memory unindexedData) private {
    242 0
            (uint256 eventAmount, uint64 eventSrcChainId, uint64 eventDstChainId, uint256 eventNonce) =
    243 0
                abi.decode(unindexedData, (uint256, uint64, uint64, uint256));
    244
    245 0
            if (eventAmount != amount) revert INVALID_PROOF_AMOUNT();
    246 0
            if (eventSrcChainId != fromChainId || uint64(chainId) != fromChainId) revert INVALID_PROOF_SOURCE_CHAIN();
    247 0
            if (eventDstChainId != _chainId) revert INVALID_PROOF_TARGETED_CHAIN();
    248 0
            if (noncesUsed[fromChainId][eventNonce]) revert NONCE_ALREADY_USED();
    249 0
            noncesUsed[fromChainId][eventNonce] = true;
    250
        }
    251
    252 0
        function _validateUnlockAssetProof(
    253
            address account,
    254
            bytes32 yieldSourceOracleId,
    255
            address token,
    256
            uint256 amount,
    257
            uint64 fromChainId,
    258
            bytes calldata proof
    259
        )
    260
            internal
    261 0
        {
    262 0
            (uint32 chainId, address emittingContract, bytes memory topics, bytes memory unindexedData) =
    263 0
                ICrossL2ProverV2(SUPER_GOVERNOR.getProver()).validateEvent(proof);
    264
    265 0
            if (uint64(chainId) != fromChainId) revert INVALID_PROOF_CHAIN();
    266
    267 0
            address vaultBank = SUPER_GOVERNOR.getVaultBank(uint64(fromChainId));
    268
    269 0
            if (emittingContract != vaultBank) revert INVALID_PROOF_EMITTER();
    270
    271 0
            _validateUnlockTopics(account, yieldSourceOracleId, token, topics);
    272 0
            _validateUnlockData(amount, fromChainId, unindexedData);
    273
        }
    274
    275 0
        function _validateUnlockTopics(address account, bytes32 yieldSourceOracleId, address token, bytes memory topics) private pure {
    276
            // topics[0] = event signature
    277 0
            if (topics.toBytes32(0) !=  IVaultBank.SuperpositionsBurned.selector) revert INVALID_PROOF_EVENT();
    278
            
    279
            // topics[1] = yieldSourceOracleId
    280 0
            if (topics.toBytes32(32) != yieldSourceOracleId) revert INVALID_PROOF_YIELD_SOURCE_ORACLE_ID();
    281
    282
            // topics[2] = account
    283 0
            if (topics.toBytes32(64) != bytes32(uint256(uint160(account)))) revert INVALID_PROOF_ACCOUNT();
    284
    285
            // topics[3] = token 
    286 0
            if (topics.toBytes32(96) != keccak256(abi.encodePacked(token))) revert INVALID_PROOF_TOKEN();
    287
        }
    288
    289 0
        function _validateUnlockData(uint256 amount, uint64 fromChainId, bytes memory unindexedData) private {
    290 0
            (uint256 eventAmount, uint64 eventChainId, uint256 eventNonce) =
    291 0
                abi.decode(unindexedData, (uint256, uint64, uint256));
    292
    293 0
            if (eventAmount != amount) revert INVALID_PROOF_AMOUNT();
    294 0
            if (eventChainId != _chainId) revert INVALID_PROOF_TARGETED_CHAIN();
    295 0
            if (noncesUsed[fromChainId][eventNonce]) revert NONCE_ALREADY_USED();
    296 0
            noncesUsed[fromChainId][eventNonce] = true;
    297
        }
    298
    }
    0% src/VaultBank/VaultBankDestination.sol
    Lines covered: 0 / 22 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    // Superform
    5
    import { VaultBankSuperPosition } from "./VaultBankSuperPosition.sol";
    6
    import { IVaultBankDestination } from "../interfaces/VaultBank/IVaultBank.sol";
    7
    8
    abstract contract VaultBankDestination is IVaultBankDestination {
    9
        /*//////////////////////////////////////////////////////////////
    10
                                     STORAGE
    11
        //////////////////////////////////////////////////////////////*/
    12
        // synthetic assets
    13
        mapping(uint64 srcChainId => mapping(bytes32 yieldSourceOracleId => mapping(address srcTokenAddress => address superPositions))) internal
    14
            _tokenToSuperPosition;
    15
        mapping(address spToken => SpAsset) internal _spAssetsInfo;
    16
    17
        /*//////////////////////////////////////////////////////////////
    18
                                     VIEW METHODS
    19
        //////////////////////////////////////////////////////////////*/
    20
        /// @inheritdoc IVaultBankDestination
    21 0
        function getSuperPositionForAsset(uint64 srcChainId, address srcAsset, bytes32 yieldSourceOracleId) external view returns (address) {
    22 0
            return _tokenToSuperPosition[srcChainId][yieldSourceOracleId][srcAsset];
    23
        }
    24
    25
        /// @inheritdoc IVaultBankDestination
    26 0
        function getAssetForSuperPosition(uint64 srcChainId, address superPosition, bytes32 yieldSourceOracleId) external view returns (address) {
    27 0
            return _spAssetsInfo[superPosition].spToToken[srcChainId][yieldSourceOracleId];
    28
        }
    29
    30
        /// @inheritdoc IVaultBankDestination
    31 0
        function isSuperPositionCreated(address superPosition) external view returns (bool) {
    32 0
            return _spAssetsInfo[superPosition].wasCreated;
    33
        }
    34
    35
        /*//////////////////////////////////////////////////////////////
    36
                                PRIVATE METHODS
    37
        //////////////////////////////////////////////////////////////*/
    38 0
        function _retrieveSuperPosition(
    39
            bytes32 yieldSourceOracleId,
    40
            uint64 srcChainId,
    41
            address srcAsset,
    42
            string calldata _srcName,
    43
            string calldata _srcSymbol,
    44
            uint8 _srcDecimals
    45
        )
    46
            internal
    47 0
            returns (address)
    48
        {
    49 0
            address _created = _tokenToSuperPosition[srcChainId][yieldSourceOracleId][srcAsset];
    50 0
            if (_created != address(0)) return _created;
    51
    52 0
            _created = address(new VaultBankSuperPosition(_srcName, _srcSymbol, _srcDecimals, yieldSourceOracleId));
    53 0
            _tokenToSuperPosition[srcChainId][yieldSourceOracleId][srcAsset] = _created;
    54 0
            _spAssetsInfo[_created].spToToken[srcChainId][yieldSourceOracleId] = srcAsset;
    55 0
            _spAssetsInfo[_created].wasCreated = true;
    56 0
            return _created;
    57
        }
    58
    59 0
        function _mintSP(address account, address superPosition, uint256 amount) internal {
    60
            // at this point the asset should exist
    61 0
            if (!_spAssetsInfo[superPosition].wasCreated) revert SUPERPOSITION_ASSET_NOT_FOUND();
    62
    63
            // mint the synthetic asset
    64 0
            VaultBankSuperPosition(superPosition).mint(account, amount);
    65
        }
    66
    67 0
        function _burnSP(address account, address superPosition, uint256 amount) internal {
    68
            // at this point the asset should exist
    69 0
            if (!_spAssetsInfo[superPosition].wasCreated) revert SUPERPOSITION_ASSET_NOT_FOUND();
    70
    71 0
            if (amount > VaultBankSuperPosition(superPosition).balanceOf(account)) revert INVALID_BURN_AMOUNT();
    72
    73
            // burn the synthetic asset
    74 0
            VaultBankSuperPosition(superPosition).burn(account, amount);
    75
        }
    76
    }
    0% src/VaultBank/VaultBankSource.sol
    Lines covered: 0 / 30 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    6
    import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    7
    import { ExcessivelySafeCall } from "excessivelySafeCall/ExcessivelySafeCall.sol";
    8
    import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    9
    10
    // Superform
    11
    import { IVaultBankSource } from "../interfaces/VaultBank/IVaultBank.sol";
    12
    13
    abstract contract VaultBankSource is IVaultBankSource {
    14
        using SafeERC20 for IERC20;
    15
        using ExcessivelySafeCall for address;
    16
        using EnumerableSet for EnumerableSet.AddressSet;
    17
    18
        /*//////////////////////////////////////////////////////////////
    19
                                     STORAGE
    20
        //////////////////////////////////////////////////////////////*/
    21
        // locked assets
    22
        EnumerableSet.AddressSet internal _lockedAssets;
    23
        mapping(address token => uint256 amount) internal _lockedAmounts;
    24
    25
        uint64 internal immutable _chainId;
    26
    27
        constructor() {
    28 0
            _chainId = uint64(block.chainid);
    29
        }
    30
    31
        /*//////////////////////////////////////////////////////////////
    32
                                     VIEW METHODS
    33
        //////////////////////////////////////////////////////////////*/
    34
        /// @inheritdoc IVaultBankSource
    35 0
        function viewTotalLockedAsset(address token) external view returns (uint256) {
    36 0
            return _lockedAmounts[token];
    37
        }
    38
    39
        /// @inheritdoc IVaultBankSource
    40 0
        function viewAllLockedAssets() external view returns (address[] memory) {
    41 0
            return _lockedAssets.values();
    42
        }
    43
    44
        /*//////////////////////////////////////////////////////////////
    45
                                     INTERNAL METHODS
    46
        //////////////////////////////////////////////////////////////*/
    47
        // ------------------ SYNTHETIC ASSETS ------------------
    48 0
        function _lockAssetForChain(
    49
            bytes32 yieldSourceOracleId,
    50
            address account,
    51
            address token,
    52
            uint256 amount,
    53
            uint64 toChainId,
    54
            uint256 nonce
    55
        )
    56
            internal
    57
        {
    58 0
            if (amount == 0) revert INVALID_AMOUNT();
    59 0
            if (token == address(0)) revert INVALID_TOKEN();
    60 0
            if (account == address(0)) revert INVALID_ACCOUNT();
    61 0
            if (yieldSourceOracleId == bytes32(0)) revert INVALID_YIELD_SOURCE_ORACLE_ID();
    62
    63 0
            if (!_lockedAssets.contains(token)) {
    64 0
                _lockedAssets.add(token);
    65
            }
    66 0
            _lockedAmounts[token] += amount;
    67
    68 0
            IERC20(token).safeTransferFrom(account, address(this), amount);
    69 0
            emit SharesLocked(yieldSourceOracleId, account, token, amount, _chainId, toChainId, nonce);
    70
        }
    71
    72 0
        function _releaseAssetFromChain(
    73
            bytes32 yieldSourceOracleId,
    74
            address account,
    75
            address token,
    76
            uint256 amount,
    77
            uint64 fromChainId,
    78
            uint256 nonce
    79
        )
    80
            internal
    81
        {
    82 0
            if (account == address(0)) revert INVALID_ACCOUNT();
    83 0
            if (token == address(0)) revert INVALID_TOKEN();
    84 0
            if (amount == 0 || amount > _lockedAmounts[token]) revert INVALID_AMOUNT();
    85 0
            if (yieldSourceOracleId == bytes32(0)) revert INVALID_YIELD_SOURCE_ORACLE_ID();
    86
    87 0
            _lockedAmounts[token] -= amount;
    88 0
            if (_lockedAmounts[token] == 0) {
    89 0
                _lockedAssets.remove(token);
    90
            }
    91
    92 0
            IERC20(token).safeTransfer(account, amount);
    93 0
            emit SharesUnlocked(yieldSourceOracleId, account, token, amount, _chainId, fromChainId, nonce);
    94
        }
    95
        // ------------------ MANAGE REWARDS ------------------
    96
    97 0
        function _claimRewards(
    98
            address target,
    99
            uint256 gasLimit,
    100
            uint256 value,
    101
            uint16 maxReturnDataCopy,
    102
            bytes calldata data
    103
        )
    104
            internal
    105 0
            returns (bytes memory)
    106
        {
    107 0
            if (target == address(0) || target == address(this)) revert INVALID_CLAIM_TARGET();
    108 0
            (bool success, bytes memory result) = target.excessivelySafeCall(gasLimit, value, maxReturnDataCopy, data);
    109 0
            if (!success) revert CLAIM_FAILED();
    110
            return result;
    111
        }
    112
    }
    0% src/VaultBank/VaultBankSuperPosition.sol
    Lines covered: 0 / 14 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
    5
    import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    6
    import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
    7
    import { ISuperPositions } from "../interfaces/VaultBank/ISuperPositions.sol";
    8
    9 0
    contract VaultBankSuperPosition is ERC20, Ownable2Step, ISuperPositions {
    10
        /*//////////////////////////////////////////////////////////////
    11
                                     STORAGE
    12
        //////////////////////////////////////////////////////////////*/
    13 0
        bytes32 public yieldSourceOracleId;
    14
        uint8 private _decimals;
    15
    16
        /// @dev `msg.sender` is VaultBank
    17 0
        constructor(
    18
            string memory name,
    19
            string memory symbol,
    20
            uint8 decimals_,
    21
            bytes32 yieldSourceOracleId_
    22 0
        ) ERC20(name, symbol) Ownable(msg.sender) {
    23 0
            if (decimals_ == 0) revert INVALID_DECIMALS();
    24 0
            if (yieldSourceOracleId_ == bytes32(0)) revert INVALID_YIELD_SOURCE_ORACLE_ID();
    25 0
            _decimals = decimals_;
    26 0
            yieldSourceOracleId = yieldSourceOracleId_;
    27
        }
    28
    29
        /*//////////////////////////////////////////////////////////////
    30
                                  VIEW METHODS
    31
        //////////////////////////////////////////////////////////////*/
    32
        /// @notice Get the number of decimals for the token
    33 0
        function decimals() public view override returns (uint8) {
    34 0
            return _decimals;
    35
        }
    36
        /*//////////////////////////////////////////////////////////////
    37
                                EXTERNAL METHODS
    38
        //////////////////////////////////////////////////////////////*/
    39
        /// @notice Mint tokens to the specified address
    40
        /// @param to_ The address to mint tokens to
    41
        /// @param amount_ The amount of tokens to mint
    42 0
        function mint(address to_, uint256 amount_) external onlyOwner {
    43 0
            _mint(to_, amount_);
    44
        }
    45
    46
        /// @notice Burn tokens from the specified address
    47
        /// @param from_ The address to burn tokens from
    48
        /// @param amount_ The amount of tokens to burn
    49 0
        function burn(address from_, uint256 amount_) external onlyOwner {
    50 0
            _burn(from_, amount_);
    51
        }
    52
    }
    90% src/libraries/AssetMetadataLib.sol
    Lines covered: 9 / 10 (90%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
    5
    6
    /// @title AssetMetadataLib
    7
    /// @author Superform Labs
    8
    /// @notice Library for handling ERC20 metadata operations
    9 0
    library AssetMetadataLib {
    10
        error INVALID_ASSET();
    11
    12
        /**
    13
         * @notice Attempts to fetch an asset's decimals
    14
         * @dev A return value of false indicates that the attempt failed in some way
    15
         * @param asset_ The address of the token to query
    16
         * @return ok Boolean indicating if the operation was successful
    17
         * @return assetDecimals The token's decimals if successful, 0 otherwise
    18
         */
    19 12×
        function tryGetAssetDecimals(address asset_) internal view returns (bool ok, uint8 assetDecimals) {
    20 29×
            if(asset_.code.length == 0) revert INVALID_ASSET();
    21
            
    22 16×
            (bool success, bytes memory encodedDecimals) =
    23 158×
                address(asset_).staticcall(abi.encodeCall(IERC20Metadata.decimals, ()));
    24 31×
            if (success && encodedDecimals.length >= 32) {
    25 34×
                uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
    26 10×
                if (returnedDecimals <= type(uint8).max) {
    27
                    return (true, uint8(returnedDecimals));
    28
                }
    29
            }
    30
            return (false, 0);
    31
        }
    32
    }
    0% src/libraries/SuperAssetPriceLib.sol
    Lines covered: 0 / 76 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity 0.8.30;
    3
    4
    import { ISuperOracle } from "../interfaces/oracles/ISuperOracle.sol";
    5
    import { ISuperAsset } from "../interfaces/SuperAsset/ISuperAsset.sol";
    6
    import { IYieldSourceOracle } from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
    7
    8
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    9
    import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    10
    import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
    11
    12 0
    library SuperAssetPriceLib {
    13
        using Math for uint256;
    14
    15
        /// @dev Gets the price of a token with circuit breakers
    16
        /// @param args The arguments for the price calculation
    17
        /// @return priceUSD The price of the token in USD
    18
        /// @return isDepeg Whether the token is depegged
    19
        /// @return isDispersion Whether the token has price dispersion
    20
        /// @return isOracleOff Whether the oracle is off
    21 0
        function getPriceWithCircuitBreakers(ISuperAsset.PriceArgs memory args)
    22
            external
    23
            view
    24 0
            returns (uint256 priceUSD, bool isDepeg, bool isDispersion, bool isOracleOff)
    25
        {
    26
            // Get token decimals
    27 0
            uint256 stddev;
    28 0
            uint256 M;
    29
    30
            // @dev Passing oneUnit to get the price of a single unit of asset to check if it has depegged
    31 0
            ISuperOracle superOracle = ISuperOracle(args.superOracle);
    32 0
            ISuperAsset superAsset = ISuperAsset(args.superAsset);
    33
    34 0
            uint256 precision = superAsset.getPrecision();
    35
    36 0
            (priceUSD, stddev, M) = _getPriceInfo(superOracle, superAsset, args.usd, args.token);
    37
    38
            // Circuit Breaker for Oracle Off
    39 0
            if (M == 0) {
    40 0
                isOracleOff = true;
    41
            } else {
    42 0
                address primaryAsset = superAsset.getPrimaryAsset();
    43 0
                if (primaryAsset == args.usd) {
    44 0
                    return (precision, false, false, false);
    45
                }
    46
    47
                // Circuit Breaker for Depeg - price deviates more than ±2% from expected
    48 0
                (isDepeg, isDispersion) = _getDepegAndDispersion(args, precision, priceUSD, stddev);
    49
            }
    50 0
            return (priceUSD, isDepeg, isDispersion, isOracleOff);
    51
        }
    52
    53
        /// @dev Derives the price of the token from the underlying vault
    54
        /// @param token The address of the token to derive the price of
    55
        /// @return priceUSD The price of the token in USD
    56
        /// @return stddev The standard deviation of the token
    57
        /// @return M The number of quote providers
    58 0
        function _derivePriceFromUnderlyingVault(
    59
            ISuperOracle superOracle,
    60
            ISuperAsset superAsset,
    61
            address USD,
    62
            address token,
    63
            address oracle
    64
        )
    65
            internal
    66
            view
    67 0
            returns (uint256 priceUSD, uint256 stddev, uint256 M)
    68 0
        {
    69 0
            address vaultAsset = IERC4626(token).asset();
    70 0
            uint256 unitVaultAsset = 10 ** IERC20Metadata(vaultAsset).decimals();
    71
    72 0
            bytes32 AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
    73
    74 0
            try superOracle.getQuoteFromProvider(unitVaultAsset, vaultAsset, USD, AVERAGE_PROVIDER) returns (
    75
                uint256 _priceUSD, uint256 _stddev, uint256, uint256 _m
    76
            ) {
    77 0
                priceUSD = _priceUSD;
    78 0
                stddev = _stddev;
    79 0
                M = _m;
    80
            } catch {
    81 0
                priceUSD = superOracle.getEmergencyPrice(vaultAsset);
    82 0
                stddev = 0;
    83 0
                M = 0;
    84
            }
    85
    86 0
            uint256 pricePerShare = IYieldSourceOracle(oracle).getPricePerShare(token);
    87 0
            if (priceUSD > 0) {
    88 0
                priceUSD = pricePerShare.mulDiv(priceUSD, superAsset.getPrecision(), Math.Rounding.Floor);
    89
            }
    90
        }
    91
    92
        /// @dev Checks if the standard deviation is greater than the dispersion threshold
    93
        /// @param stddev The standard deviation
    94
        /// @param priceUSD The price in USD
    95
        /// @return isDispersion True if the standard deviation is greater than the dispersion threshold
    96 0
        function _isSTDDevDegged(
    97
            address superAsset,
    98
            uint256 stddev,
    99
            uint256 priceUSD,
    100
            uint256 dispersionThreshold
    101
        )
    102
            internal
    103
            pure
    104 0
            returns (bool)
    105
        {
    106
            // Calculate relative standard deviation
    107 0
            uint256 relativeStdDev = Math.mulDiv(stddev, ISuperAsset(superAsset).getPrecision(), priceUSD);
    108
    109
            // Circuit Breaker for Dispersion
    110 0
            if (relativeStdDev > dispersionThreshold) {
    111 0
                return true;
    112
            }
    113 0
            return false;
    114
        }
    115
    116
        /// @dev Checks if the token is depegged
    117
        /// @param priceUSD The price of the token in USD
    118
        /// @param assetPriceUSD The price of the asset in USD
    119
        /// @return isDepeg True if the token is depegged
    120 0
        function _isTokenDepeg(
    121
            uint256 priceUSD,
    122
            uint256 precision,
    123
            uint256 assetPriceUSD,
    124
            uint256 depegLowerThreshold,
    125
            uint256 depegUpperThreshold
    126
        )
    127
            internal
    128
            pure
    129 0
            returns (bool isDepeg)
    130 0
        {
    131
            // NOTE: There should be no need to adjust for decimals since
    132
            // the token specific decimals and
    133
            // the Oracle Price decimals
    134
            // can be different
    135
            // Example, if we send 2 USDC to someone then the transferred amount is 2e6 since USDC has 6d
    136
            // but the USDC price quoted in USD can have its own decimals, for example
    137
            // if USDC depegs high and is worth 3 USD then its price quoted in a 18d oracle will be 3e18
    138 0
            uint256 ratio = Math.mulDiv(priceUSD, precision, assetPriceUSD);
    139
    140 0
            if (ratio < depegLowerThreshold || ratio > depegUpperThreshold) {
    141 0
                isDepeg = true;
    142
            }
    143
        }
    144
    145
        /// @dev Gets the price information for a token
    146
        /// @param superOracle The super oracle
    147
        /// @param superAsset The super asset
    148
        /// @param USD The USD address
    149
        /// @param token The token address
    150
        /// @return priceUSD The price of the token in USD
    151
        /// @return stddev The standard deviation of the token
    152 0
        function _getPriceInfo(
    153
            ISuperOracle superOracle,
    154
            ISuperAsset superAsset,
    155
            address USD,
    156
            address token
    157
        )
    158
            internal
    159
            view
    160 0
            returns (uint256 priceUSD, uint256 stddev, uint256 M)
    161 0
        {
    162 0
            bytes32 AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
    163 0
            uint256 one = 10 ** IERC20Metadata(token).decimals();
    164
    165 0
            ISuperAsset.TokenData memory tokenData = superAsset.getTokenData(token);
    166
    167 0
            if (tokenData.isSupportedERC20) {
    168 0
                try superOracle.getQuoteFromProvider(one, token, USD, AVERAGE_PROVIDER) returns (
    169
                    uint256 _priceUSD, uint256 _stddev, uint256, uint256 _m
    170
                ) {
    171 0
                    priceUSD = _priceUSD;
    172 0
                    stddev = _stddev;
    173 0
                    M = _m;
    174
                } catch {
    175 0
                    priceUSD = superOracle.getEmergencyPrice(token);
    176 0
                    M = 0;
    177
                }
    178 0
            } else if (tokenData.isSupportedUnderlyingVault) {
    179 0
                (priceUSD, stddev, M) =
    180 0
                    _derivePriceFromUnderlyingVault(superOracle, superAsset, USD, token, tokenData.oracle);
    181
            }
    182
        }
    183
    184
        /// @dev Gets the depeg and dispersion status of a token
    185
        /// @param args The arguments for the price calculation
    186
        /// @param precision The precision of the token
    187
        /// @param priceUSD The price of the token in USD
    188
        /// @param stddev The standard deviation of the token
    189
        /// @return isDepeg True if the token is depegged
    190
        /// @return isDispersion True if the token has price dispersion
    191 0
        function _getDepegAndDispersion(
    192
            ISuperAsset.PriceArgs memory args,
    193
            uint256 precision,
    194
            uint256 priceUSD,
    195
            uint256 stddev
    196
        )
    197
            internal
    198
            view
    199 0
            returns (bool isDepeg, bool isDispersion)
    200 0
        {
    201 0
            uint256 assetPriceUSD = _getAssetPriceUSD(args.superOracle, args.superAsset, args.usd);
    202
    203 0
            isDepeg = _isTokenDepeg(priceUSD, precision, assetPriceUSD, args.depegLowerThreshold, args.depegUpperThreshold);
    204
    205 0
            isDispersion = _isSTDDevDegged(args.superAsset, stddev, priceUSD, args.dispersionThreshold);
    206
        }
    207
    208
        /// @dev Gets the price of the asset in USD
    209
        /// @param superOracleAddress The address of the super oracle
    210
        /// @param superAssetAddress The address of the super asset
    211
        /// @param USD The address of the USD token
    212
        /// @return assetPriceUSD The price of the asset in USD
    213 0
        function _getAssetPriceUSD(
    214
            address superOracleAddress,
    215
            address superAssetAddress,
    216
            address USD
    217
        )
    218
            internal
    219
            view
    220 0
            returns (uint256 assetPriceUSD)
    221 0
        {
    222 0
            bytes32 AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
    223 0
            address primaryAsset = ISuperAsset(superAssetAddress).getPrimaryAsset();
    224 0
            uint256 oneUnitAsset = 10 ** IERC20Metadata(primaryAsset).decimals();
    225
    226 0
            ISuperOracle superOracle = ISuperOracle(superOracleAddress);
    227
    228 0
            try superOracle.getQuoteFromProvider(oneUnitAsset, primaryAsset, USD, AVERAGE_PROVIDER) returns (
    229
                uint256 _priceUSD, uint256, uint256, uint256
    230
            ) {
    231 0
                assetPriceUSD = _priceUSD;
    232
            } catch {
    233 0
                assetPriceUSD = superOracle.getEmergencyPrice(primaryAsset);
    234
            }
    235
        }
    236
    }
    0% src/oracles/ECDSAPPSOracle.sol
    Lines covered: 0 / 127 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // External
    5
    import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
    6
    import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
    7
    import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
    8
    9
    // Superform
    10
    import { ISuperGovernor } from "../interfaces/ISuperGovernor.sol";
    11
    import { ISuperVaultAggregator } from "../interfaces/SuperVault/ISuperVaultAggregator.sol";
    12
    import { IECDSAPPSOracle } from "../interfaces/oracles/IECDSAPPSOracle.sol";
    13
    14
    /// @title ECDSAPPSOracle
    15
    /// @author Superform Labs
    16
    /// @notice PPS Oracle that validates price updates using ECDSA signatures
    17
    /// @dev Implements the IECDSAPPSOracle interface for validating and forwarding PPS updates
    18 0
    contract ECDSAPPSOracle is IECDSAPPSOracle, EIP712 {
    19
        using ECDSA for bytes32;
    20
        using MessageHashUtils for bytes32;
    21
    22
        /*//////////////////////////////////////////////////////////////
    23
                                     STORAGE
    24
        //////////////////////////////////////////////////////////////*/
    25 0
        mapping(address _strategy => uint256 _nonce) public noncePerStrategy;
    26
    27
        /// @notice The SuperGovernor contract for validator verification
    28 0
        ISuperGovernor public immutable SUPER_GOVERNOR;
    29 0
        bytes32 public constant UPDATE_PPS_TYPEHASH = keccak256(
    30
            "UpdatePPS(address strategy,uint256 pps,uint256 ppsStdev,uint256 validatorSet,uint256 totalValidators,uint256 timestamp, uint256 strategyNonce)"
    31
        );
    32
    33 0
        bytes32 private constant SUPER_VAULT_AGGREGATOR = keccak256("SUPER_VAULT_AGGREGATOR");
    34
    35
        /*//////////////////////////////////////////////////////////////
    36
                                  CONSTRUCTOR
    37
        //////////////////////////////////////////////////////////////*/
    38
        /// @notice Initializes the ECDSAPPSOracle contract
    39
        /// @param superGovernor_ Address of the SuperGovernor contract
    40 0
        constructor(address superGovernor_, string memory name_, string memory version_) EIP712(name_, version_) {
    41 0
            if (superGovernor_ == address(0)) revert INVALID_VALIDATOR();
    42
    43 0
            SUPER_GOVERNOR = ISuperGovernor(superGovernor_);
    44
        }
    45
    46
        /*//////////////////////////////////////////////////////////////
    47
                                  VIEW FUNCTIONS
    48
        //////////////////////////////////////////////////////////////*/
    49
        /// @inheritdoc IECDSAPPSOracle
    50 0
        function domainSeparator() external view returns (bytes32) {
    51 0
            return _domainSeparatorV4();
    52
        }
    53
    54
        /*//////////////////////////////////////////////////////////////
    55
                             PPS UPDATE FUNCTIONS
    56
        //////////////////////////////////////////////////////////////*/
    57
        /// @inheritdoc IECDSAPPSOracle
    58 0
        function updatePPS(UpdatePPSArgs calldata args) external {
    59 0
            uint256 strategiesLength = args.strategies.length;
    60
            
    61 0
            if (strategiesLength == 0) revert ZERO_LENGTH_ARRAY();
    62
            // Validate input array lengths
    63 0
            if (    strategiesLength != args.proofsArray.length
    64 0
                    || strategiesLength != args.ppss.length
    65 0
                    || strategiesLength != args.ppsStdevs.length || strategiesLength != args.validatorSets.length
    66 0
                    || strategiesLength != args.timestamps.length || strategiesLength != args.totalValidators.length
    67 0
            ) revert ARRAY_LENGTH_MISMATCH();
    68
    69
    70
            // Process strategies and collect valid entries
    71 0
            (
    72 0
                address[] memory validStrategies,
    73 0
                uint256[] memory validPpss,
    74 0
                uint256[] memory validPpsStdevs,
    75 0
                uint256[] memory validValidatorSets,
    76 0
                uint256[] memory validTotalValidators,
    77 0
                uint256[] memory validTimestamps
    78 0
            ) = _processBatchStrategies(args, strategiesLength);
    79
    80
            // Forward valid entries if any exist
    81 0
            _forwardValidEntries(
    82 0
                validStrategies,
    83 0
                validPpss,
    84 0
                validPpsStdevs,
    85 0
                validValidatorSets,
    86 0
                validTotalValidators,
    87 0
                validTimestamps
    88
            );
    89
        }
    90
    91
        
    92
    93
        /// @notice Validates an array of proofs for a strategy's PPS update
    94
        /// @param params Validation parameters
    95
        /// @dev Reverts immediately if duplicate signers are found or quorum is not met
    96 0
        function validateProofs(IECDSAPPSOracle.ValidationParams memory params)
    97
            public
    98
            view
    99
        {
    100 0
            _validateProofs(params);
    101
        }
    102
    103
        /*//////////////////////////////////////////////////////////////
    104
                                INTERNAL FUNCTIONS
    105
        //////////////////////////////////////////////////////////////*/
    106
        /// @notice Validates an array of proofs for a strategy's PPS update
    107
        /// @param params Validation parameters
    108
        /// @dev Reverts immediately if duplicate signers are found or quorum is not met
    109 0
        function _validateProofs(IECDSAPPSOracle.ValidationParams memory params) internal view {
    110
            // Check if this oracle is the active PPS Oracle
    111 0
            if (!SUPER_GOVERNOR.isActivePPSOracle(address(this))) revert NOT_ACTIVE_PPS_ORACLE();
    112
    113
            // Create message hash with all parameters- If anyare incorrect, the message hash will be different and the
    114
            // derived signer address will be incorrect- resulting in a revert
    115 0
            bytes32 structHash = keccak256(
    116 0
                abi.encodePacked(
    117
                    UPDATE_PPS_TYPEHASH,
    118 0
                    params.strategy,
    119 0
                    params.pps,
    120 0
                    params.ppsStdev,
    121 0
                    params.validatorSet,
    122 0
                    params.totalValidators,
    123 0
                    params.timestamp,
    124 0
                    noncePerStrategy[params.strategy]
    125
                )
    126
            );
    127 0
            bytes32 digest = _hashTypedDataV4(structHash);
    128
            
    129 0
            uint256 proofsLength = params.proofs.length;
    130 0
            if (proofsLength == 0) revert ZERO_LENGTH_ARRAY();
    131
    132 0
            address lastSigner;
    133
            // Process each proof
    134 0
            for (uint256 i; i < proofsLength; i++) {
    135
                // Recover the signer from the proof
    136 0
                address signer = ECDSA.recover(digest, params.proofs[i]);
    137
    138
                // Verify the signer is a registered validator
    139 0
                if (!SUPER_GOVERNOR.isValidator(signer)) revert INVALID_VALIDATOR();
    140
    141
                // Check for duplicates or improper ordering - signers must be in ascending order
    142 0
                if (signer <= lastSigner) revert INVALID_PROOF();
    143 0
                lastSigner = signer;
    144
            }
    145
    146
            // Validate that validatorSet matches actual number of valid signatures
    147 0
            if (params.validatorSet != proofsLength) revert INVALID_VALIDATOR_SET();
    148
    149
            // Validate that totalValidators matches actual total number of validators
    150 0
            if (params.totalValidators != SUPER_GOVERNOR.getValidators().length) revert INVALID_TOTAL_VALIDATORS();
    151
    152
            // Ensure we have enough valid signatures to meet quorum
    153 0
            if (proofsLength < SUPER_GOVERNOR.getPPSOracleQuorum()) revert QUORUM_NOT_MET();
    154
        }
    155
    156
        /// @notice Processes batch strategies and returns valid entries
    157
        /// @param args Batch update arguments
    158
        /// @param strategiesLength Length of strategies array
    159
        /// @return validStrategies Array of valid strategy addresses
    160
        /// @return validPpss Array of valid PPS values
    161
        /// @return validPpsStdevs Array of valid PPS standard deviations
    162
        /// @return validValidatorSets Array of valid validator sets
    163
        /// @return validTotalValidators Array of valid total validators
    164
        /// @return validTimestamps Array of valid timestamps
    165 0
        function _processBatchStrategies(
    166
            UpdatePPSArgs calldata args,
    167
            uint256 strategiesLength
    168
        )
    169
            internal
    170
            returns (
    171 0
                address[] memory validStrategies,
    172 0
                uint256[] memory validPpss,
    173 0
                uint256[] memory validPpsStdevs,
    174 0
                uint256[] memory validValidatorSets,
    175 0
                uint256[] memory validTotalValidators,
    176 0
                uint256[] memory validTimestamps
    177
            )
    178
        {
    179
            // Arrays to collect valid entries
    180 0
            validStrategies = new address[](strategiesLength);
    181 0
            validPpss = new uint256[](strategiesLength);
    182 0
            validPpsStdevs = new uint256[](strategiesLength);
    183 0
            validValidatorSets = new uint256[](strategiesLength);
    184 0
            validTotalValidators = new uint256[](strategiesLength);
    185 0
            validTimestamps = new uint256[](strategiesLength);
    186 0
            uint256 validCount;
    187
    188
            // Process each strategy update
    189 0
            for (uint256 i; i < strategiesLength; i++) {
    190 0
                bool isValid = _processIndividualStrategy(args, i);
    191 0
                if (isValid) {
    192
                    // Add to valid entries
    193 0
                    validStrategies[validCount] = args.strategies[i];
    194 0
                    validPpss[validCount] = args.ppss[i];
    195 0
                    validPpsStdevs[validCount] = args.ppsStdevs[i];
    196 0
                    validValidatorSets[validCount] = args.validatorSets[i];
    197 0
                    validTotalValidators[validCount] = args.totalValidators[i];
    198 0
                    validTimestamps[validCount] = args.timestamps[i];
    199 0
                    validCount++;
    200
                }
    201
            }
    202
    203
            // Resize arrays to actual valid count
    204 0
            assembly {
    205 0
                mstore(validStrategies, validCount)
    206 0
                mstore(validPpss, validCount)
    207 0
                mstore(validPpsStdevs, validCount)
    208 0
                mstore(validValidatorSets, validCount)
    209 0
                mstore(validTotalValidators, validCount)
    210 0
                mstore(validTimestamps, validCount)
    211
            }
    212
        }
    213
    214
        /// @notice Processes an individual strategy in the batch
    215
        /// @param args Batch update arguments
    216
        /// @param index Index of the strategy to process
    217
        /// @return isValid True if the strategy was processed successfully
    218 0
        function _processIndividualStrategy(
    219
            UpdatePPSArgs calldata args,
    220
            uint256 index
    221 0
        ) internal returns (bool isValid) {
    222 0
            address _strategy = args.strategies[index];
    223
    224
             // Validate proofs and check quorum requirement
    225 0
            try IECDSAPPSOracle(address(this)).validateProofs(
    226 0
                IECDSAPPSOracle.ValidationParams({
    227 0
                    strategy: _strategy,
    228 0
                    proofs: args.proofsArray[index],
    229 0
                    pps: args.ppss[index],
    230 0
                    ppsStdev: args.ppsStdevs[index],
    231 0
                    validatorSet: args.validatorSets[index],
    232 0
                    totalValidators: args.totalValidators[index],
    233 0
                    timestamp: args.timestamps[index]
    234
                })
    235
            ) {
    236 0
                emit PPSValidated(
    237
                    _strategy,
    238 0
                    args.ppss[index],
    239 0
                    args.ppsStdevs[index],
    240 0
                    args.validatorSets[index],
    241 0
                    args.totalValidators[index],
    242 0
                    args.timestamps[index],
    243 0
                    msg.sender
    244
                );
    245
            } catch Error(string memory reason) {
    246 0
                emit ProofValidationFailed(_strategy, reason);
    247 0
                return false;
    248
            } catch (bytes memory lowLevelData) {
    249 0
                emit ProofValidationFailedLowLevel(_strategy, lowLevelData);
    250
                return false;
    251
            }
    252
            
    253 0
            noncePerStrategy[_strategy]++;
    254 0
            return true;
    255
        }
    256
    257
        /// @notice Forwards valid entries to SuperVaultAggregator
    258
        /// @param validStrategies Array of valid strategy addresses
    259
        /// @param validPpss Array of valid PPS values
    260
        /// @param validPpsStdevs Array of valid PPS standard deviations
    261
        /// @param validValidatorSets Array of valid validator sets
    262
        /// @param validTotalValidators Array of valid total validators
    263
        /// @param validTimestamps Array of valid timestamps
    264 0
        function _forwardValidEntries(
    265
            address[] memory validStrategies,
    266
            uint256[] memory validPpss,
    267
            uint256[] memory validPpsStdevs,
    268
            uint256[] memory validValidatorSets,
    269
            uint256[] memory validTotalValidators,
    270
            uint256[] memory validTimestamps
    271
        ) internal {
    272
            // Only forward if there are valid entries
    273 0
            if (validStrategies.length > 0) {
    274 0
                try ISuperVaultAggregator(SUPER_GOVERNOR.getAddress(SUPER_VAULT_AGGREGATOR)).forwardPPS(
    275 0
                    ISuperVaultAggregator.ForwardPPSArgs({
    276 0
                        strategies: validStrategies,
    277 0
                        ppss: validPpss,
    278 0
                        ppsStdevs: validPpsStdevs,
    279 0
                        validatorSets: validValidatorSets,
    280 0
                        totalValidators: validTotalValidators,
    281 0
                        timestamps: validTimestamps,
    282 0
                        updateAuthority: msg.sender
    283
                    })
    284
                ) {
    285 0
                } catch Error(string memory reason) {
    286 0
                    emit BatchForwardPPSFailed(reason);
    287
                } catch (bytes memory lowLevelData) {
    288 0
                    emit BatchForwardPPSFailedLowLevel(lowLevelData);
    289
                }
    290
            }
    291
        }
    292
    293
    }
    0% src/oracles/SuperOracle.sol
    Lines covered: 0 / 3 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // Superform
    5
    import { SuperOracleBase } from "./SuperOracleBase.sol";
    6
    7
    /// @title SuperOracle
    8
    /// @author Superform Labs
    9
    /// @notice Oracle for Superform
    10 0
    contract SuperOracle is SuperOracleBase {
    11 0
        constructor(
    12
            address superGovernor_,
    13
            address[] memory bases,
    14
            address[] memory quotes,
    15
            bytes32[] memory providers,
    16
            address[] memory feeds
    17
        )
    18 0
            SuperOracleBase(superGovernor_, bases, quotes, providers, feeds)
    19
        { }
    20
    }
    0% src/oracles/SuperOracleBase.sol
    Lines covered: 0 / 210 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // external
    5
    import { IOracle } from "../vendor/awesome-oracles/IOracle.sol";
    6
    import { AggregatorV3Interface } from "../vendor/chainlink/AggregatorV3Interface.sol";
    7
    import { IERC20 } from "forge-std/interfaces/IERC20.sol";
    8
    import { BoringERC20 } from "../vendor/BoringSolidity/BoringERC20.sol";
    9
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    10
    11
    // Superform
    12
    import { ISuperOracle } from "../interfaces/oracles/ISuperOracle.sol";
    13
    14
    /// @title SuperOracle
    15
    /// @author Superform Labs
    16
    /// @notice Oracle for Superform
    17
    abstract contract SuperOracleBase is ISuperOracle, IOracle {
    18
        using BoringERC20 for IERC20;
    19
    20
        /// @notice Mapping of feed to max staleness period
    21 0
        mapping(address feed => uint256 maxStaleness) public feedMaxStaleness;
    22
    23
        /// @notice Mapping of token to emergency price when oracle is down
    24 0
        mapping(address token => uint256 emergencyPrice) public emergencyPrices;
    25
    26 0
        uint256 public maxDefaultStaleness;
    27
    28
        /// @notice Pending oracle update
    29 0
        PendingUpdate public pendingUpdate;
    30
    31
        /// @notice Pending provider removal
    32 0
        PendingRemoval public pendingRemoval;
    33
    34
        /// @notice Mapping of base asset to array of oracle providers to oracle feed address
    35
        mapping(address base => mapping(address quote => mapping(bytes32 provider => address feed))) internal oracles;
    36
    37
        /// @notice Array of active provider ids
    38 0
        bytes32[] public activeProviders;
    39 0
        mapping(bytes32 provider => bool isSet) public isProviderSet;
    40
    41
        /// @notice Timelock period for oracle updates
    42 0
        uint256 internal constant TIMELOCK_PERIOD = 1 weeks;
    43
        uint256 internal constant MAX_SAMPLE_PROVIDERS = 10;
    44 0
        bytes32 internal constant AVERAGE_PROVIDER = keccak256("AVERAGE_PROVIDER");
    45
    46
        // SuperGovernor address
    47 0
        address public immutable SUPER_GOVERNOR;
    48
    49 0
        constructor(
    50
            address superGovernor_,
    51
            address[] memory bases,
    52
            address[] memory quotes,
    53
            bytes32[] memory providers,
    54
            address[] memory feeds
    55 0
        ) {
    56 0
            if (superGovernor_ == address(0)) revert ZERO_ADDRESS();
    57 0
            SUPER_GOVERNOR = superGovernor_;
    58 0
            maxDefaultStaleness = 1 days;
    59
    60
            // validate oracle inputs
    61 0
            _validateOracleInputs(bases, quotes, providers, feeds);
    62
    63
            // configure oracles
    64 0
            _configureOracles(bases, quotes, providers, feeds);
    65
    66
            // set default staleness for feeds
    67 0
            uint256 length = feeds.length;
    68 0
            for (uint256 i; i < length; ++i) {
    69 0
                feedMaxStaleness[feeds[i]] = maxDefaultStaleness;
    70
            }
    71
        }
    72
    73
        /*//////////////////////////////////////////////////////////////
    74
                                EXTERNAL FUNCTIONS
    75
        //////////////////////////////////////////////////////////////*/
    76
        /// @inheritdoc ISuperOracle
    77 0
        function setMaxStaleness(uint256 newMaxStaleness) external {
    78 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    79 0
            maxDefaultStaleness = newMaxStaleness;
    80 0
            emit MaxStalenessUpdated(newMaxStaleness);
    81
        }
    82
    83
        /// @inheritdoc ISuperOracle
    84 0
        function setFeedMaxStaleness(address feed, uint256 newMaxStaleness) external {
    85 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    86 0
            _setFeedMaxStaleness(feed, newMaxStaleness);
    87
        }
    88
    89
        /// @inheritdoc ISuperOracle
    90 0
        function setEmergencyPrice(address token_, uint256 price_) external {
    91 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    92 0
            _setEmergencyPrice(token_, price_);
    93
        }
    94
    95
        /// @inheritdoc ISuperOracle
    96 0
        function batchSetEmergencyPrice(address[] calldata tokens_, uint256[] calldata prices_) external {
    97 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    98 0
            uint256 length = tokens_.length;
    99 0
            if (length == 0) revert ZERO_ARRAY_LENGTH();
    100 0
            if (length != prices_.length) revert ARRAY_LENGTH_MISMATCH();
    101
    102 0
            for (uint256 i; i < length; ++i) {
    103 0
                _setEmergencyPrice(tokens_[i], prices_[i]);
    104
            }
    105
        }
    106
    107
        /// @inheritdoc ISuperOracle
    108 0
        function setFeedMaxStalenessBatch(address[] calldata feeds, uint256[] calldata newMaxStalenessList) external {
    109 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    110 0
            uint256 length = feeds.length;
    111 0
            if (length == 0) revert ZERO_ARRAY_LENGTH();
    112 0
            if (length != newMaxStalenessList.length) {
    113 0
                revert ARRAY_LENGTH_MISMATCH();
    114
            }
    115
    116 0
            for (uint256 i; i < length; ++i) {
    117 0
                _setFeedMaxStaleness(feeds[i], newMaxStalenessList[i]);
    118
            }
    119
        }
    120
    121
        /// @inheritdoc ISuperOracle
    122 0
        function queueOracleUpdate(
    123
            address[] calldata bases,
    124
            address[] calldata quotes,
    125
            bytes32[] calldata providers,
    126
            address[] calldata feeds
    127
        )
    128
            external
    129 0
        {
    130 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    131 0
            uint256 length = bases.length;
    132 0
            if (length != quotes.length || length != providers.length || length != feeds.length) {
    133 0
                revert ARRAY_LENGTH_MISMATCH();
    134
            }
    135
    136 0
            _validateOracleInputs(bases, quotes, providers, feeds);
    137
    138 0
            pendingUpdate = PendingUpdate({
    139 0
                bases: bases,
    140 0
                quotes: quotes,
    141 0
                providers: providers,
    142 0
                feeds: feeds,
    143 0
                timestamp: block.timestamp
    144
            });
    145
    146 0
            emit OracleUpdateQueued(bases, quotes, providers, feeds, block.timestamp);
    147
        }
    148
    149
        /// @inheritdoc ISuperOracle
    150 0
        function executeOracleUpdate() external {
    151 0
            if (pendingUpdate.timestamp == 0) revert NO_PENDING_UPDATE();
    152 0
            if (block.timestamp < pendingUpdate.timestamp + TIMELOCK_PERIOD) revert TIMELOCK_NOT_ELAPSED();
    153
    154 0
            _configureOracles(pendingUpdate.bases, pendingUpdate.quotes, pendingUpdate.providers, pendingUpdate.feeds);
    155
    156 0
            emit OracleUpdateExecuted(
    157 0
                pendingUpdate.bases, pendingUpdate.quotes, pendingUpdate.providers, pendingUpdate.feeds
    158
            );
    159
    160 0
            delete pendingUpdate;
    161
        }
    162
    163
        /// @inheritdoc ISuperOracle
    164 0
        function getOracleAddress(address base, address quote, bytes32 provider) external view returns (address oracle) {
    165 0
            oracle = oracles[base][quote][provider];
    166 0
            if (oracle == address(0)) revert NO_ORACLES_CONFIGURED();
    167 0
            if (!isProviderSet[provider]) return address(0);
    168
        }
    169
    170
        /// @inheritdoc ISuperOracle
    171 0
        function queueProviderRemoval(bytes32[] calldata providers) external {
    172 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    173 0
            if (pendingRemoval.timestamp != 0) revert PENDING_UPDATE_EXISTS();
    174
    175 0
            uint256 length = providers.length;
    176 0
            if (length == 0) revert ZERO_ARRAY_LENGTH();
    177
    178 0
            pendingRemoval = PendingRemoval({ providers: providers, timestamp: block.timestamp });
    179
    180 0
            emit ProviderRemovalQueued(providers, block.timestamp);
    181
        }
    182
    183
        /// @inheritdoc ISuperOracle
    184 0
        function executeProviderRemoval() external {
    185 0
            if (pendingRemoval.timestamp == 0) revert NO_PENDING_UPDATE();
    186 0
            if (block.timestamp < pendingRemoval.timestamp + TIMELOCK_PERIOD) revert TIMELOCK_NOT_ELAPSED();
    187
    188 0
            bytes32[] memory providersToRemove = pendingRemoval.providers;
    189
    190
            // Loop through each provider to remove
    191 0
            for (uint256 i; i < providersToRemove.length; i++) {
    192 0
                bytes32 providerToRemove = providersToRemove[i];
    193 0
                isProviderSet[providerToRemove] = false;
    194
    195
                // Find the provider in activeProviders array
    196 0
                for (uint256 j; j < activeProviders.length; j++) {
    197 0
                    if (activeProviders[j] == providerToRemove) {
    198
                        // Replace the provider to remove with the last provider in the array
    199 0
                        if (j < activeProviders.length - 1) {
    200 0
                            activeProviders[j] = activeProviders[activeProviders.length - 1];
    201
                        }
    202
    203
                        // Remove the last element
    204 0
                        activeProviders.pop();
    205 0
                        break;
    206
                    }
    207
                }
    208
            }
    209
    210 0
            emit ProviderRemovalExecuted(providersToRemove);
    211
    212 0
            delete pendingRemoval;
    213
        }
    214
    215
        /// @inheritdoc ISuperOracle
    216 0
        function getActiveProviders() external view returns (bytes32[] memory) {
    217 0
            return activeProviders;
    218
        }
    219
    220
        /*//////////////////////////////////////////////////////////////
    221
                            EXTERNAL VIEW FUNCTIONS
    222
        //////////////////////////////////////////////////////////////*/
    223
        /// @inheritdoc ISuperOracle
    224 0
        function getQuoteFromProvider(
    225
            uint256 baseAmount,
    226
            address base,
    227
            address quote,
    228
            bytes32 oracleProvider
    229
        )
    230
            public
    231
            view
    232
            virtual
    233 0
            returns (uint256 quoteAmount, uint256 deviation, uint256 totalProviders, uint256 availableProviders)
    234
        {
    235
            // If average, calculate average of all oracles
    236 0
            if (oracleProvider == AVERAGE_PROVIDER) {
    237 0
                uint256 length = activeProviders.length;
    238 0
                uint256[] memory validQuotes = new uint256[](length);
    239 0
                uint256 count;
    240 0
                (quoteAmount, validQuotes, totalProviders, count) = _getAverageQuote(base, quote, baseAmount, length);
    241
                availableProviders = count;
    242 0
                deviation = _calculateStdDev(validQuotes, count);
    243
            } else {
    244 0
                quoteAmount = _getQuoteFromOracle(oracles[base][quote][oracleProvider], baseAmount, base, quote, true);
    245 0
                deviation = 0;
    246 0
                totalProviders = 1;
    247 0
                availableProviders = 1;
    248
            }
    249
        }
    250
    251
        /// @inheritdoc ISuperOracle
    252 0
        function getEmergencyPrice(address token) external view returns (uint256) {
    253 0
            return emergencyPrices[token];
    254
        }
    255
    256
        /// @inheritdoc IOracle
    257 0
        function getQuote(
    258
            uint256 baseAmount,
    259
            address base,
    260
            address quote
    261
        )
    262
            external
    263
            view
    264
            virtual
    265 0
            returns (uint256 quoteAmount)
    266
        {
    267
            // using IOracle interface we always assume average provider
    268 0
            (quoteAmount,,,) = getQuoteFromProvider(baseAmount, base, quote, AVERAGE_PROVIDER);
    269
        }
    270
    271
        /*//////////////////////////////////////////////////////////////
    272
                                INTERNAL FUNCTIONS
    273
        //////////////////////////////////////////////////////////////*/
    274 0
        function _validateOracleInputs(
    275
            address[] memory bases,
    276
            address[] memory quotes,
    277
            bytes32[] memory providers,
    278
            address[] memory feeds
    279
        )
    280
            internal
    281
            pure
    282 0
        {
    283 0
            uint256 length = bases.length;
    284 0
            for (uint256 i; i < length; ++i) {
    285 0
                address base = bases[i];
    286 0
                address quote = quotes[i];
    287 0
                bytes32 provider = providers[i];
    288 0
                address feed = feeds[i];
    289
    290 0
                if (provider == bytes32(0)) revert ZERO_PROVIDER();
    291 0
                if (provider == AVERAGE_PROVIDER) revert AVERAGE_PROVIDER_NOT_ALLOWED();
    292 0
                if (base == address(0) || quote == address(0) || feed == address(0)) revert ZERO_ADDRESS();
    293
            }
    294
        }
    295
    296 0
        function _setFeedMaxStaleness(address feed, uint256 newMaxStaleness) internal {
    297 0
            if (newMaxStaleness > maxDefaultStaleness) {
    298 0
                revert MAX_STALENESS_EXCEEDED();
    299
            }
    300 0
            if (newMaxStaleness == 0) {
    301 0
                newMaxStaleness = maxDefaultStaleness;
    302
            }
    303 0
            feedMaxStaleness[feed] = newMaxStaleness;
    304 0
            emit FeedMaxStalenessUpdated(feed, newMaxStaleness);
    305
        }
    306
    307 0
        function _getQuoteFromOracle(
    308
            address oracle,
    309
            uint256 baseAmount,
    310
            address base,
    311
            address quote,
    312
            bool revertOnError
    313
        )
    314
            internal
    315
            view
    316
            virtual
    317 0
            returns (uint256 quoteAmount)
    318 0
        {
    319 0
            int256 answer;
    320 0
            uint256 updatedAt;
    321
    322
            // --- Get round data ---
    323 0
            try AggregatorV3Interface(oracle).latestRoundData() returns (
    324
                uint80, int256 _answer, uint256, uint256 _updatedAt, uint80
    325
            ) {
    326 0
                answer = _answer;
    327 0
                updatedAt = _updatedAt;
    328
            } catch {
    329 0
                if (revertOnError) revert ORACLE_ROUND_DATA_CALL_FAIL(oracle);
    330 0
                return 0;
    331
            }
    332
    333
            // --- Validate data ---
    334 0
            if (answer <= 0 || block.timestamp - updatedAt > feedMaxStaleness[oracle]) {
    335 0
                if (revertOnError) revert ORACLE_UNTRUSTED_DATA();
    336
                return 0;
    337
            }
    338
    339
            // --- Get decimals and compute scaled amount ---
    340 0
            try AggregatorV3Interface(oracle).decimals() returns (uint8 feedDecimals) {
    341 0
                uint8 baseDecimals = IERC20(base).safeDecimals();
    342 0
                uint8 quoteDecimals = IERC20(quote).safeDecimals();
    343
    344
                // Calculate quote amount with proper decimal scaling
    345 0
                quoteAmount = Math.mulDiv(baseAmount, uint256(answer), 10 ** feedDecimals);
    346 0
                quoteAmount = Math.mulDiv(quoteAmount, 10 ** quoteDecimals, 10 ** baseDecimals);
    347
            } catch {
    348 0
                if (revertOnError) revert ORACLE_DECIMALS_CALL_FAIL(oracle);
    349
                return 0;
    350
            }
    351
        }
    352
    353
    354 0
        function _getAverageQuote(
    355
            address base,
    356
            address quote,
    357
            uint256 baseAmount,
    358
            uint256 numberOfProviders
    359
        )
    360
            internal
    361
            view
    362
            virtual
    363 0
            returns (uint256 quoteAmount, uint256[] memory validQuotes, uint256 totalCount, uint256 count)
    364 0
        {
    365 0
            uint256 total;
    366 0
            validQuotes = new uint256[](numberOfProviders);
    367
    368
            // Loop through all active providers
    369
            // Iterates only until MAX_SAMPLE_PROVIDERS valid quotes are collected to bound gas usage
    370 0
            for (uint256 i; i < numberOfProviders; ++i) {
    371 0
                bytes32 provider = activeProviders[i];
    372 0
                address providerOracle = oracles[base][quote][provider];
    373
                // provider is in active list but has no available oracle address
    374
                /*
    375
                base = ETH
    376
                quote = [USD, EUR]
    377
                providers = [CHAINLINK, eORACLE]
    378
    379
                Let's say we have
    380
    381
                ETH -> USD -> CHAINLINK -> address(0x1)
    382
                ETH -> USD - eORACLE -> address(0x2)
    383
                ETH -> EUR -> CHAINLINK -> address(0x3)
    384
    385
                This would just continue for 
    386
    387
                ETH -> EUR -> eOracle, because oracle address is 0
    388
                */
    389 0
                if (providerOracle == address(0)) continue;
    390
    391
                // We have one more registered oracle for this base asset
    392
                unchecked {
    393 0
                    ++totalCount;
    394
                }
    395
    396 0
                uint256 quote_ = _getQuoteFromOracle(providerOracle, baseAmount, base, quote, false);
    397
    398
                /// @dev we don't revert on error, we just skip the oracle value
    399 0
                if (quote_ > 0) {
    400 0
                    total += quote_;
    401 0
                    validQuotes[count] = quote_;
    402
                    // This oracle is available
    403
                    unchecked {
    404 0
                        ++count;
    405
                    }
    406 0
                    if (count == MAX_SAMPLE_PROVIDERS) {
    407 0
                        break;
    408
                    }
    409
                }
    410
            }
    411 0
            if (count == 0) revert NO_VALID_REPORTED_PRICES();
    412
    413 0
            quoteAmount = total / count;
    414
        }
    415
    416 0
        function _getOracleDecimals(AggregatorV3Interface oracle_) internal view virtual returns (uint8) {
    417 0
            return oracle_.decimals();
    418
        }
    419
    420 0
        function _calculateStdDev(uint256[] memory values, uint256 length) internal pure virtual returns (uint256 stddev) {
    421
            uint256 sum = 0;
    422
            uint256 count = 0;
    423 0
            for (uint256 i; i < length; ++i) {
    424 0
                sum += values[i];
    425 0
                count++;
    426
            }
    427 0
            if (count < 2) return 0;
    428
    429 0
            uint256 mean = sum / count;
    430 0
            uint256 sumSquaredDiff = 0;
    431 0
            for (uint256 i; i < length; ++i) {
    432 0
                uint256 diff;
    433 0
                if (values[i] >= mean) {
    434 0
                    diff = values[i] - mean;
    435
                } else {
    436 0
                    diff = mean - values[i];
    437
                }
    438
    439 0
                uint256 squaredDiff = Math.mulDiv(diff, diff, 1);
    440 0
                sumSquaredDiff += squaredDiff;
    441
            }
    442
    443 0
            uint256 variance = sumSquaredDiff / count;
    444 0
            return _sqrt(variance);
    445
        }
    446
    447 0
        function _sqrt(uint256 x) internal pure returns (uint256 y) {
    448 0
            if (x == 0) return 0;
    449
    450 0
            uint256 z = (x + 1) / 2;
    451 0
            y = x;
    452
    453 0
            while (z < y) {
    454 0
                y = z;
    455 0
                z = (x / z + z) / 2;
    456
            }
    457
        }
    458
    459 0
        function _configureOracles(
    460
            address[] memory bases,
    461
            address[] memory quotes,
    462
            bytes32[] memory providers,
    463
            address[] memory feeds
    464
        )
    465
            internal
    466
        {
    467 0
            uint256 length = bases.length;
    468
    469 0
            for (uint256 i; i < length; ++i) {
    470 0
                address base = bases[i];
    471 0
                address quote = quotes[i];
    472 0
                bytes32 provider = providers[i];
    473 0
                address feed = feeds[i];
    474
    475 0
                oracles[base][quote][provider] = feed;
    476
    477
                // Update activeProviders array - add provider if not already present
    478
                bool providerExists = false;
    479 0
                uint256 activeProvidersLength = activeProviders.length;
    480 0
                for (uint256 j; j < activeProvidersLength; ++j) {
    481 0
                    if (activeProviders[j] == provider) {
    482 0
                        providerExists = true;
    483 0
                        break;
    484
                    }
    485
                }
    486
    487 0
                if (!providerExists) {
    488 0
                    activeProviders.push(provider);
    489 0
                    isProviderSet[provider] = true;
    490
                }
    491
            }
    492
        }
    493
    494
        /// @notice Internal logic to set an emergency price for a token.
    495
        /// @param token_ The address of the token.
    496
        /// @param price_ The emergency price to set.
    497 0
        function _setEmergencyPrice(address token_, uint256 price_) internal {
    498 0
            emergencyPrices[token_] = price_;
    499 0
            emit EmergencyPriceUpdated(token_, price_);
    500
        }
    501
    }
    0% src/oracles/SuperOracleL2.sol
    Lines covered: 0 / 49 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    // Superform
    5
    6
    import { SuperOracleBase } from "./SuperOracleBase.sol";
    7
    import { ISuperOracleL2 } from "../interfaces/oracles/ISuperOracleL2.sol";
    8
    9
    // external
    10
    import { IERC20 } from "forge-std/interfaces/IERC20.sol";
    11
    import { BoringERC20 } from "../vendor/BoringSolidity/BoringERC20.sol";
    12
    import { AggregatorV3Interface } from "../vendor/chainlink/AggregatorV3Interface.sol";
    13
    import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
    14
    15
    /// @title SuperOracleL2
    16
    /// @author Superform Labs
    17
    /// @notice Layer 2 Oracle for Superform
    18 0
    contract SuperOracleL2 is SuperOracleBase, ISuperOracleL2 {
    19
        using BoringERC20 for IERC20;
    20
    21
        /*//////////////////////////////////////////////////////////////
    22
                                STORAGE
    23
        //////////////////////////////////////////////////////////////*/
    24 0
        mapping(address dataOracle => address uptimeOracle) public uptimeFeeds;
    25 0
        mapping(address uptimeOracle => uint256 gracePeriod) public gracePeriods;
    26
    27 0
        uint256 private constant DEFAULT_GRACE_PERIOD_TIME = 3600;
    28
    29 0
        constructor(
    30
            address superGovernor_,
    31
            address[] memory bases,
    32
            address[] memory quotes,
    33
            bytes32[] memory providers,
    34
            address[] memory feeds
    35
        )
    36 0
            SuperOracleBase(superGovernor_, bases, quotes, providers, feeds)
    37
        { }
    38
    39
        /*//////////////////////////////////////////////////////////////
    40
                                EXTERNAL FUNCTIONS
    41
        //////////////////////////////////////////////////////////////*/
    42
        /// @inheritdoc ISuperOracleL2
    43 0
        function batchSetUptimeFeed(
    44
            address[] calldata dataOracles,
    45
            address[] calldata uptimeOracles,
    46
            uint256[] calldata gracePeriods_
    47
        )
    48
            external
    49 0
        {
    50 0
            if (msg.sender != SUPER_GOVERNOR) revert UNAUTHORIZED_UPDATE_AUTHORITY();
    51
    52 0
            uint256 length = dataOracles.length;
    53 0
            if (length == 0) revert ZERO_ADDRESS(); // Reusing error code
    54 0
            if (length != uptimeOracles.length || length != gracePeriods_.length) {
    55 0
                revert ARRAY_LENGTH_MISMATCH();
    56
            }
    57
    58 0
            for (uint256 i; i < length; ++i) {
    59 0
                address dataOracle = dataOracles[i];
    60 0
                address uptimeOracle = uptimeOracles[i];
    61 0
                uint256 gracePeriod = gracePeriods_[i];
    62
    63 0
                if (dataOracle == address(0) || uptimeOracle == address(0)) revert ZERO_ADDRESS();
    64
    65 0
                uptimeFeeds[dataOracle] = uptimeOracle;
    66 0
                gracePeriods[uptimeOracle] = gracePeriod;
    67
    68 0
                emit UptimeFeedSet(dataOracle, uptimeOracle);
    69 0
                emit GracePeriodSet(uptimeOracle, gracePeriod);
    70
            }
    71
        }
    72
    73
        /*//////////////////////////////////////////////////////////////
    74
                                INTERNAL FUNCTIONS
    75
        //////////////////////////////////////////////////////////////*/
    76 0
        function _getQuoteFromOracle(
    77
            address oracle,
    78
            uint256 baseAmount,
    79
            address base,
    80
            address quote,
    81
            bool revertOnError
    82
        )
    83
            internal
    84
            view
    85
            override
    86 0
            returns (uint256 quoteAmount)
    87 0
        {
    88 0
            {
    89 0
                address uptimeOracle = uptimeFeeds[oracle];
    90 0
                if (uptimeOracle == address(0)) revert NO_UPTIME_FEED();
    91
    92 0
                (
    93
                    /*uint80 roundID*/
    94
                    ,
    95 0
                    int256 uptimeAnswer,
    96 0
                    uint256 startedAt,
    97
                    /*uint256 updatedAt*/
    98
                    ,
    99
                    /*uint80 answeredInRound*/
    100 0
                ) = AggregatorV3Interface(uptimeOracle).latestRoundData();
    101
    102
                // Answer == 0: Sequencer is up
    103
                // Answer == 1: Sequencer is down
    104 0
                bool isSequencerUp = uptimeAnswer == 0;
    105 0
                if (!isSequencerUp) {
    106 0
                    revert SEQUENCER_DOWN();
    107
                }
    108
    109
                // Make sure the grace period has passed after the
    110
                // sequencer is back up.
    111 0
                uint256 timeSinceUp = block.timestamp - startedAt;
    112 0
                uint256 gracePeriod = gracePeriods[uptimeOracle];
    113 0
                if (gracePeriod == 0) {
    114
                    gracePeriod = DEFAULT_GRACE_PERIOD_TIME;
    115
                }
    116 0
                if (timeSinceUp <= gracePeriod) {
    117 0
                    revert GRACE_PERIOD_NOT_OVER();
    118
                }
    119
            }
    120
    121 0
            (, int256 answer,, uint256 updatedAt,) = AggregatorV3Interface(oracle).latestRoundData();
    122
    123
            // Validate data
    124 0
            if (answer <= 0 || block.timestamp - updatedAt > feedMaxStaleness[oracle]) {
    125 0
                if (revertOnError) revert ORACLE_UNTRUSTED_DATA();
    126 0
                return 0;
    127
            }
    128
    129
            // Get decimals
    130 0
            uint8 feedDecimals = _getOracleDecimals(AggregatorV3Interface(oracle));
    131 0
            uint8 baseDecimals = IERC20(base).safeDecimals();
    132 0
            uint8 quoteDecimals = IERC20(quote).safeDecimals();
    133
    134
            // Calculate quote amount with proper decimal scaling
    135 0
            quoteAmount = Math.mulDiv(baseAmount, uint256(answer), 10 ** feedDecimals);
    136 0
            quoteAmount = Math.mulDiv(quoteAmount, 10 ** quoteDecimals, 10 ** baseDecimals);
    137
        }
    138
    }
    0% src/vendor/BoringSolidity/BoringERC20.sol
    Lines covered: 0 / 4 (0%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    // With thanks to @BoringCrypto
    3
    pragma solidity >=0.8.19;
    4
    5
    import { IERC20 } from "forge-std/interfaces/IERC20.sol";
    6
    7
    // solhint-disable avoid-low-level-calls
    8
    9 0
    library BoringERC20 {
    10
        bytes4 private constant SIG_DECIMALS = 0x313ce567; // decimals()
    11
    12
        /// @notice Provides a safe ERC20.decimals version which returns '18' as fallback value.
    13
        /// @param token The address of the ERC-20 token contract.
    14
        /// @return (uint8) Token decimals.
    15 0
        function safeDecimals(IERC20 token) internal view returns (uint8) {
    16 0
            (bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_DECIMALS));
    17 0
            return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;
    18
        }
    19
    }
    0% src/vendor/BytesLib.sol
    Lines covered: 0 / 4 (0%)
    1
    // SPDX-License-Identifier: Unlicense
    2
    /*
    3
     * @title Solidity Bytes Arrays Utils
    4
     * @author Gonçalo Sá <goncalo.sa@consensys.net>
    5
     *
    6
     * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity.
    7
     *      The library lets you concatenate, slice and type cast bytes arrays both in memory and storage.
    8
     */
    9
    pragma solidity 0.8.30;
    10
    11
    12 0
    library BytesLib {
    13
        function concat(
    14
            bytes memory _preBytes,
    15
            bytes memory _postBytes
    16
        )
    17
            internal
    18
            pure
    19
            returns (bytes memory)
    20
        {
    21
            bytes memory tempBytes;
    22
    23
            assembly {
    24
                // Get a location of some free memory and store it in tempBytes as
    25
                // Solidity does for memory variables.
    26
                tempBytes := mload(0x40)
    27
    28
                // Store the length of the first bytes array at the beginning of
    29
                // the memory for tempBytes.
    30
                let length := mload(_preBytes)
    31
                mstore(tempBytes, length)
    32
    33
                // Maintain a memory counter for the current write location in the
    34
                // temp bytes array by adding the 32 bytes for the array length to
    35
                // the starting location.
    36
                let mc := add(tempBytes, 0x20)
    37
                // Stop copying when the memory counter reaches the length of the
    38
                // first bytes array.
    39
                let end := add(mc, length)
    40
    41
                for {
    42
                    // Initialize a copy counter to the start of the _preBytes data,
    43
                    // 32 bytes into its memory.
    44
                    let cc := add(_preBytes, 0x20)
    45
                } lt(mc, end) {
    46
                    // Increase both counters by 32 bytes each iteration.
    47
                    mc := add(mc, 0x20)
    48
                    cc := add(cc, 0x20)
    49
                } {
    50
                    // Write the _preBytes data into the tempBytes memory 32 bytes
    51
                    // at a time.
    52
                    mstore(mc, mload(cc))
    53
                }
    54
    55
                // Add the length of _postBytes to the current length of tempBytes
    56
                // and store it as the new length in the first 32 bytes of the
    57
                // tempBytes memory.
    58
                length := mload(_postBytes)
    59
                mstore(tempBytes, add(length, mload(tempBytes)))
    60
    61
                // Move the memory counter back from a multiple of 0x20 to the
    62
                // actual end of the _preBytes data.
    63
                mc := end
    64
                // Stop copying when the memory counter reaches the new combined
    65
                // length of the arrays.
    66
                end := add(mc, length)
    67
    68
                for {
    69
                    let cc := add(_postBytes, 0x20)
    70
                } lt(mc, end) {
    71
                    mc := add(mc, 0x20)
    72
                    cc := add(cc, 0x20)
    73
                } {
    74
                    mstore(mc, mload(cc))
    75
                }
    76
    77
                // Update the free-memory pointer by padding our last write location
    78
                // to 32 bytes: add 31 bytes to the end of tempBytes to move to the
    79
                // next 32 byte block, then round down to the nearest multiple of
    80
                // 32. If the sum of the length of the two arrays is zero then add
    81
                // one before rounding down to leave a blank 32 bytes (the length block with 0).
    82
                mstore(0x40, and(
    83
                  add(add(end, iszero(add(length, mload(_preBytes)))), 31),
    84
                  not(31) // Round down to the nearest 32 bytes.
    85
                ))
    86
            }
    87
    88
            return tempBytes;
    89
        }
    90
    91
        function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
    92
            assembly {
    93
                // Read the first 32 bytes of _preBytes storage, which is the length
    94
                // of the array. (We don't need to use the offset into the slot
    95
                // because arrays use the entire slot.)
    96
                let fslot := sload(_preBytes.slot)
    97
                // Arrays of 31 bytes or less have an even value in their slot,
    98
                // while longer arrays have an odd value. The actual length is
    99
                // the slot divided by two for odd values, and the lowest order
    100
                // byte divided by two for even values.
    101
                // If the slot is even, bitwise and the slot with 255 and divide by
    102
                // two to get the length. If the slot is odd, bitwise and the slot
    103
                // with -1 and divide by two.
    104
                let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
    105
                let mlength := mload(_postBytes)
    106
                let newlength := add(slength, mlength)
    107
                // slength can contain both the length and contents of the array
    108
                // if length < 32 bytes so let's prepare for that
    109
                // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
    110
                switch add(lt(slength, 32), lt(newlength, 32))
    111
                case 2 {
    112
                    // Since the new array still fits in the slot, we just need to
    113
                    // update the contents of the slot.
    114
                    // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
    115
                    sstore(
    116
                        _preBytes.slot,
    117
                        // all the modifications to the slot are inside this
    118
                        // next block
    119
                        add(
    120
                            // we can just add to the slot contents because the
    121
                            // bytes we want to change are the LSBs
    122
                            fslot,
    123
                            add(
    124
                                mul(
    125
                                    div(
    126
                                        // load the bytes from memory
    127
                                        mload(add(_postBytes, 0x20)),
    128
                                        // zero all bytes to the right
    129
                                        exp(0x100, sub(32, mlength))
    130
                                    ),
    131
                                    // and now shift left the number of bytes to
    132
                                    // leave space for the length in the slot
    133
                                    exp(0x100, sub(32, newlength))
    134
                                ),
    135
                                // increase length by the double of the memory
    136
                                // bytes length
    137
                                mul(mlength, 2)
    138
                            )
    139
                        )
    140
                    )
    141
                }
    142
                case 1 {
    143
                    // The stored value fits in the slot, but the combined value
    144
                    // will exceed it.
    145
                    // get the keccak hash to get the contents of the array
    146
                    mstore(0x0, _preBytes.slot)
    147
                    let sc := add(keccak256(0x0, 0x20), div(slength, 32))
    148
    149
                    // save new length
    150
                    sstore(_preBytes.slot, add(mul(newlength, 2), 1))
    151
    152
                    // The contents of the _postBytes array start 32 bytes into
    153
                    // the structure. Our first read should obtain the `submod`
    154
                    // bytes that can fit into the unused space in the last word
    155
                    // of the stored array. To get this, we read 32 bytes starting
    156
                    // from `submod`, so the data we read overlaps with the array
    157
                    // contents by `submod` bytes. Masking the lowest-order
    158
                    // `submod` bytes allows us to add that value directly to the
    159
                    // stored value.
    160
    161
                    let submod := sub(32, slength)
    162
                    let mc := add(_postBytes, submod)
    163
                    let end := add(_postBytes, mlength)
    164
                    let mask := sub(exp(0x100, submod), 1)
    165
    166
                    sstore(
    167
                        sc,
    168
                        add(
    169
                            and(
    170
                                fslot,
    171
                                0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
    172
                            ),
    173
                            and(mload(mc), mask)
    174
                        )
    175
                    )
    176
    177
                    for {
    178
                        mc := add(mc, 0x20)
    179
                        sc := add(sc, 1)
    180
                    } lt(mc, end) {
    181
                        sc := add(sc, 1)
    182
                        mc := add(mc, 0x20)
    183
                    } {
    184
                        sstore(sc, mload(mc))
    185
                    }
    186
    187
                    mask := exp(0x100, sub(mc, end))
    188
    189
                    sstore(sc, mul(div(mload(mc), mask), mask))
    190
                }
    191
                default {
    192
                    // get the keccak hash to get the contents of the array
    193
                    mstore(0x0, _preBytes.slot)
    194
                    // Start copying to the last used word of the stored array.
    195
                    let sc := add(keccak256(0x0, 0x20), div(slength, 32))
    196
    197
                    // save new length
    198
                    sstore(_preBytes.slot, add(mul(newlength, 2), 1))
    199
    200
                    // Copy over the first `submod` bytes of the new data as in
    201
                    // case 1 above.
    202
                    let slengthmod := mod(slength, 32)
    203
                    let mlengthmod := mod(mlength, 32)
    204
                    let submod := sub(32, slengthmod)
    205
                    let mc := add(_postBytes, submod)
    206
                    let end := add(_postBytes, mlength)
    207
                    let mask := sub(exp(0x100, submod), 1)
    208
    209
                    sstore(sc, add(sload(sc), and(mload(mc), mask)))
    210
    211
                    for {
    212
                        sc := add(sc, 1)
    213
                        mc := add(mc, 0x20)
    214
                    } lt(mc, end) {
    215
                        sc := add(sc, 1)
    216
                        mc := add(mc, 0x20)
    217
                    } {
    218
                        sstore(sc, mload(mc))
    219
                    }
    220
    221
                    mask := exp(0x100, sub(mc, end))
    222
    223
                    sstore(sc, mul(div(mload(mc), mask), mask))
    224
                }
    225
            }
    226
        }
    227
    228
        function slice(
    229
            bytes memory _bytes,
    230
            uint256 _start,
    231
            uint256 _length
    232
        )
    233
            internal
    234
            pure
    235
            returns (bytes memory)
    236
        {
    237
            // We're using the unchecked block below because otherwise execution ends 
    238
            // with the native overflow error code.
    239
            unchecked {
    240
                require(_length + 31 >= _length, "slice_overflow");
    241
            }
    242
            require(_bytes.length >= _start + _length, "slice_outOfBounds");
    243
    244
            bytes memory tempBytes;
    245
    246
            assembly {
    247
                switch iszero(_length)
    248
                case 0 {
    249
                    // Get a location of some free memory and store it in tempBytes as
    250
                    // Solidity does for memory variables.
    251
                    tempBytes := mload(0x40)
    252
    253
                    // The first word of the slice result is potentially a partial
    254
                    // word read from the original array. To read it, we calculate
    255
                    // the length of that partial word and start copying that many
    256
                    // bytes into the array. The first word we copy will start with
    257
                    // data we don't care about, but the last `lengthmod` bytes will
    258
                    // land at the beginning of the contents of the new array. When
    259
                    // we're done copying, we overwrite the full first word with
    260
                    // the actual length of the slice.
    261
                    let lengthmod := and(_length, 31)
    262
    263
                    // The multiplication in the next line is necessary
    264
                    // because when slicing multiples of 32 bytes (lengthmod == 0)
    265
                    // the following copy loop was copying the origin's length
    266
                    // and then ending prematurely not copying everything it should.
    267
                    let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
    268
                    let end := add(mc, _length)
    269
    270
                    for {
    271
                        // The multiplication in the next line has the same exact purpose
    272
                        // as the one above.
    273
                        let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
    274
                    } lt(mc, end) {
    275
                        mc := add(mc, 0x20)
    276
                        cc := add(cc, 0x20)
    277
                    } {
    278
                        mstore(mc, mload(cc))
    279
                    }
    280
    281
                    mstore(tempBytes, _length)
    282
    283
                    //update free-memory pointer
    284
                    //allocating the array padded to 32 bytes like the compiler does now
    285
                    mstore(0x40, and(add(mc, 31), not(31)))
    286
                }
    287
                //if we want a zero-length slice let's just return a zero-length array
    288
                default {
    289
                    tempBytes := mload(0x40)
    290
                    //zero out the 32 bytes slice we are about to return
    291
                    //we need to do it because Solidity does not garbage collect
    292
                    mstore(tempBytes, 0)
    293
    294
                    mstore(0x40, add(tempBytes, 0x20))
    295
                }
    296
            }
    297
    298
            return tempBytes;
    299
        }
    300
    301
        function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
    302
            require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
    303
            address tempAddress;
    304
    305
            assembly {
    306
                tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
    307
            }
    308
    309
            return tempAddress;
    310
        }
    311
    312
        function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
    313
            require(_bytes.length >= _start + 1 , "toUint8_outOfBounds");
    314
            uint8 tempUint;
    315
    316
            assembly {
    317
                tempUint := mload(add(add(_bytes, 0x1), _start))
    318
            }
    319
    320
            return tempUint;
    321
        }
    322
    323
        function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) {
    324
            require(_bytes.length >= _start + 2, "toUint16_outOfBounds");
    325
            uint16 tempUint;
    326
    327
            assembly {
    328
                tempUint := mload(add(add(_bytes, 0x2), _start))
    329
            }
    330
    331
            return tempUint;
    332
        }
    333
    334
        function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) {
    335
            require(_bytes.length >= _start + 4, "toUint32_outOfBounds");
    336
            uint32 tempUint;
    337
    338
            assembly {
    339
                tempUint := mload(add(add(_bytes, 0x4), _start))
    340
            }
    341
    342
            return tempUint;
    343
        }
    344
    345
        function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) {
    346
            require(_bytes.length >= _start + 8, "toUint64_outOfBounds");
    347
            uint64 tempUint;
    348
    349
            assembly {
    350
                tempUint := mload(add(add(_bytes, 0x8), _start))
    351
            }
    352
    353
            return tempUint;
    354
        }
    355
    356
        function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) {
    357
            require(_bytes.length >= _start + 12, "toUint96_outOfBounds");
    358
            uint96 tempUint;
    359
    360
            assembly {
    361
                tempUint := mload(add(add(_bytes, 0xc), _start))
    362
            }
    363
    364
            return tempUint;
    365
        }
    366
    367
        function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) {
    368
            require(_bytes.length >= _start + 16, "toUint128_outOfBounds");
    369
            uint128 tempUint;
    370
    371
            assembly {
    372
                tempUint := mload(add(add(_bytes, 0x10), _start))
    373
            }
    374
    375
            return tempUint;
    376
        }
    377
    378
        function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
    379
            require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
    380
            uint256 tempUint;
    381
    382
            assembly {
    383
                tempUint := mload(add(add(_bytes, 0x20), _start))
    384
            }
    385
    386
            return tempUint;
    387
        }
    388
    389 0
        function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) {
    390 0
            require(_bytes.length >= _start + 32, "toBytes32_outOfBounds");
    391
            bytes32 tempBytes32;
    392
    393
            assembly {
    394 0
                tempBytes32 := mload(add(add(_bytes, 0x20), _start))
    395
            }
    396
    397
            return tempBytes32;
    398
        }
    399
    400
        function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
    401
            bool success = true;
    402
    403
            assembly {
    404
                let length := mload(_preBytes)
    405
    406
                // if lengths don't match the arrays are not equal
    407
                switch eq(length, mload(_postBytes))
    408
                case 1 {
    409
                    // cb is a circuit breaker in the for loop since there's
    410
                    //  no said feature for inline assembly loops
    411
                    // cb = 1 - don't breaker
    412
                    // cb = 0 - break
    413
                    let cb := 1
    414
    415
                    let mc := add(_preBytes, 0x20)
    416
                    let end := add(mc, length)
    417
    418
                    for {
    419
                        let cc := add(_postBytes, 0x20)
    420
                    // the next line is the loop condition:
    421
                    // while(uint256(mc < end) + cb == 2)
    422
                    } eq(add(lt(mc, end), cb), 2) {
    423
                        mc := add(mc, 0x20)
    424
                        cc := add(cc, 0x20)
    425
                    } {
    426
                        // if any of these checks fails then arrays are not equal
    427
                        if iszero(eq(mload(mc), mload(cc))) {
    428
                            // unsuccess:
    429
                            success := 0
    430
                            cb := 0
    431
                        }
    432
                    }
    433
                }
    434
                default {
    435
                    // unsuccess:
    436
                    success := 0
    437
                }
    438
            }
    439
    440
            return success;
    441
        }
    442
    443
        function equalStorage(
    444
            bytes storage _preBytes,
    445
            bytes memory _postBytes
    446
        )
    447
            internal
    448
            view
    449
            returns (bool)
    450
        {
    451
            bool success = true;
    452
    453
            assembly {
    454
                // we know _preBytes_offset is 0
    455
                let fslot := sload(_preBytes.slot)
    456
                // Decode the length of the stored array like in concatStorage().
    457
                let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
    458
                let mlength := mload(_postBytes)
    459
    460
                // if lengths don't match the arrays are not equal
    461
                switch eq(slength, mlength)
    462
                case 1 {
    463
                    // slength can contain both the length and contents of the array
    464
                    // if length < 32 bytes so let's prepare for that
    465
                    // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
    466
                    if iszero(iszero(slength)) {
    467
                        switch lt(slength, 32)
    468
                        case 1 {
    469
                            // blank the last byte which is the length
    470
                            fslot := mul(div(fslot, 0x100), 0x100)
    471
    472
                            if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
    473
                                // unsuccess:
    474
                                success := 0
    475
                            }
    476
                        }
    477
                        default {
    478
                            // cb is a circuit breaker in the for loop since there's
    479
                            //  no said feature for inline assembly loops
    480
                            // cb = 1 - don't breaker
    481
                            // cb = 0 - break
    482
                            let cb := 1
    483
    484
                            // get the keccak hash to get the contents of the array
    485
                            mstore(0x0, _preBytes.slot)
    486
                            let sc := keccak256(0x0, 0x20)
    487
    488
                            let mc := add(_postBytes, 0x20)
    489
                            let end := add(mc, mlength)
    490
    491
                            // the next line is the loop condition:
    492
                            // while(uint256(mc < end) + cb == 2)
    493
                            for {} eq(add(lt(mc, end), cb), 2) {
    494
                                sc := add(sc, 1)
    495
                                mc := add(mc, 0x20)
    496
                            } {
    497
                                if iszero(eq(sload(sc), mload(mc))) {
    498
                                    // unsuccess:
    499
                                    success := 0
    500
                                    cb := 0
    501
                                }
    502
                            }
    503
                        }
    504
                    }
    505
                }
    506
                default {
    507
                    // unsuccess:
    508
                    success := 0
    509
                }
    510
            }
    511
    512
            return success;
    513
        }
    514
    }
    100% test/recon/BeforeAfter.sol
    Lines covered: 86 / 86 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    5
    import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    6
    import {MockERC20} from "@recon/MockERC20.sol";
    7
    8
    import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
    9
    10
    import {Setup} from "./Setup.sol";
    11
    12
    enum OpType {
    13
        DEFAULT,
    14
        ADD,
    15
        REMOVE,
    16
        FULFILL,
    17
        REQUEST,
    18
        TRANSFER,
    19
        CANCEL
    20
    }
    21
    22
    // ghost variables for tracking state variable values before and after function calls
    23
    abstract contract BeforeAfter is Setup {
    24
        struct Vars {
    25
            mapping(address user => uint256 pendingAsAssets) pendingUserAssets;
    26
            mapping(address user => uint256 claimableAsAssets) claimableUserAssets;
    27
            mapping(address user => ISuperVaultStrategy.SuperVaultState) state;
    28
            mapping(address user => uint256 shares) superVaultShares;
    29
            uint256 oraclePPS;
    30
            uint256 naivePPS;
    31
            uint256 summedTotalShares;
    32
            uint256 summedAccumulatorShares;
    33
            uint256 summedAccumulatorCostBasis;
    34
            uint256 summedTotalAssets;
    35
            uint256 strategyAssetBalance;
    36
            uint256 summedPendingRedeem;
    37
        }
    38
    39
        Vars internal _before;
    40
        Vars internal _after;
    41
        OpType internal _currentOp;
    42
    43
        modifier updateGhosts() {
    44 18×
            _currentOp = OpType.DEFAULT;
    45 12×
            __before();
    46
            _;
    47
            __after();
    48
        }
    49
    50
        modifier updateGhostsWithOpType(OpType op) {
    51 90×
            _currentOp = op;
    52 36×
            __before();
    53
            _;
    54 17×
            __after();
    55
        }
    56
    57
        function __before() internal {
    58
            (
    59
                _before.summedAccumulatorShares,
    60
                _before.summedAccumulatorCostBasis
    61
            ) = _sumSuperVaultValues();
    62
            _before.naivePPS = _calculateNaivePPS();
    63
            _before.summedTotalShares = _sumTotalShares();
    64
            _before.summedTotalAssets = _sumStrategyAssets();
    65
            _before.summedPendingRedeem = _sumRequestedRedemptions();
    66
    67 22×
            _before.pendingUserAssets[_getActor()] = _getPendingAsAssets();
    68 27×
            _before.claimableUserAssets[_getActor()] = _getClaimableAsAssets();
    69 128×
            _before.state[_getActor()] = superVaultStrategy.getSuperVaultState(
    70
                _getActor()
    71
            );
    72 81×
            _before.superVaultShares[_getActor()] = superVault.balanceOf(
    73
                _getActor()
    74
            );
    75
    76 125×
            _before.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf(
    77
                address(superVaultStrategy)
    78
            );
    79 60×
            _before.oraclePPS = superVaultAggregator.getPPS(
    80
                address(superVaultStrategy)
    81
            );
    82
        }
    83
    84
        function __after() internal {
    85
            (
    86
                _after.summedAccumulatorShares,
    87
                _after.summedAccumulatorCostBasis
    88
            ) = _sumSuperVaultValues();
    89 12×
            _after.naivePPS = _calculateNaivePPS();
    90 12×
            _after.summedTotalShares = _sumTotalShares();
    91 12×
            _after.summedTotalAssets = _sumStrategyAssets();
    92 12×
            _after.summedPendingRedeem = _sumRequestedRedemptions();
    93
    94 44×
            _after.pendingUserAssets[_getActor()] = _getPendingAsAssets();
    95 54×
            _after.claimableUserAssets[_getActor()] = _getClaimableAsAssets();
    96 256×
            _after.state[_getActor()] = superVaultStrategy.getSuperVaultState(
    97
                _getActor()
    98
            );
    99 162×
            _after.superVaultShares[_getActor()] = superVault.balanceOf(
    100
                _getActor()
    101
            );
    102
    103 252×
            _after.strategyAssetBalance = MockERC20(superVault.asset()).balanceOf(
    104 10×
                address(superVaultStrategy)
    105
            );
    106 118×
            _after.oraclePPS = superVaultAggregator.getPPS(
    107 10×
                address(superVaultStrategy)
    108
            );
    109
        }
    110
    111
        // Helpers
    112
    113
        /// @dev total shares in the system is the sum of shares in the escrow and held by all users
    114
        function _sumTotalShares() internal returns (uint256) {
    115 14×
            address[] memory actors = _getActors();
    116
            uint256 totalShares;
    117
    118 142×
            totalShares += superVault.balanceOf(address(superVaultEscrow));
    119 26×
            for (uint256 i; i < actors.length; i++) {
    120 182×
                totalShares += superVault.balanceOf(actors[i]);
    121
            }
    122
    123
            return totalShares;
    124
        }
    125
    126
        /// @notice Calculates the naive price per share by summing all assets across strategy and yield sources
    127
        /// @dev inspired by the share price calculation from BaseSuperVaultTest::_updateSuperVaultPPS
    128
        /// @return naivePPS The calculated price per share (scaled by 1e18)
    129
        function _calculateNaivePPS() internal returns (uint256 naivePPS) {
    130
            // Get total supply of SuperVault shares
    131 140×
            uint256 totalSupply = superVault.totalSupply();
    132
    133
            // If no shares exist, PPS is 0
    134 12×
            if (totalSupply == 0) {
    135
                return 0;
    136
            }
    137
    138
            // Calculate total assets across all locations
    139 14×
            uint256 totalAssets = _sumStrategyAssets();
    140
    141
            // Calculate naive PPS: (totalAssets * PRECISION) / totalSupply
    142
            // Using 1e18 as precision to match the system's PPS_DECIMALS
    143 158×
            naivePPS = (totalAssets * superVault.PRECISION()) / totalSupply;
    144
    145
            return naivePPS;
    146
        }
    147
    148 22×
        function _sumStrategyAssets() public view returns (uint256) {
    149
            // Get the underlying asset
    150 140×
            address asset = superVault.asset();
    151
    152
            uint256 totalAssets;
    153
            // 1. Assets held directly in SuperVaultStrategy
    154 140×
            totalAssets += IERC20(asset).balanceOf(address(superVaultStrategy));
    155
    156
            // 2. Loop through all yield sources and sum underlying asset balances
    157 12×
            address[] memory yieldSources = _getYieldSources();
    158 28×
            for (uint256 i = 0; i < yieldSources.length; i++) {
    159 44×
                if (yieldSources[i] != address(0)) {
    160
                    // Get the underlying asset balance held in each yield source
    161 170×
                    totalAssets += IERC20(asset).balanceOf(yieldSources[i]);
    162
                }
    163
            }
    164
    165
            return totalAssets;
    166
        }
    167
    168
        function _sumSuperVaultValues()
    169
            internal
    170
            view
    171
            returns (uint256 sumAccumulatorShares, uint256 sumAccumulatorCostBasis)
    172
        {
    173 14×
            address[] memory actors = _getActors();
    174
    175 30×
            for (uint256 i; i < actors.length; i++) {
    176 156×
                sumAccumulatorShares += superVaultStrategy
    177 34×
                    .getSuperVaultState(actors[i])
    178
                    .accumulatorShares;
    179 150×
                sumAccumulatorCostBasis += superVaultStrategy
    180 34×
                    .getSuperVaultState(actors[i])
    181
                    .accumulatorCostBasis;
    182
            }
    183
        }
    184
    185
        function _sumRequestedRedemptions() internal returns (uint256) {
    186 14×
            address[] memory actors = _getActors();
    187
            uint256 totalRequested;
    188 26×
            for (uint256 i; i < actors.length; i++) {
    189 192×
                totalRequested += superVault.pendingRedeemRequest(0, actors[i]);
    190
            }
    191
    192
            return totalRequested;
    193
        }
    194
    195 12×
        function _getPendingAsAssets() internal returns (uint256) {
    196 136×
            uint256 pendingRedemptions = superVault.pendingRedeemRequest(
    197
                0,
    198
                _getActor()
    199
            );
    200 120×
            uint256 pendingRedemptionsAsAssets = superVault.convertToAssets(
    201
                pendingRedemptions
    202
            );
    203
    204
            return pendingRedemptionsAsAssets;
    205
        }
    206
    207 12×
        function _getClaimableAsAssets() internal returns (uint256) {
    208
            uint256 claimableRedemptions = superVault.claimableRedeemRequest(
    209
                0,
    210
                _getActor()
    211
            );
    212
            uint256 claimableRedemptionsAsAssets = superVault.convertToAssets(
    213
                claimableRedemptions
    214
            );
    215
    216
            return claimableRedemptionsAsAssets;
    217
        }
    218
    }
    100% test/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 test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml --format text --test-limit 1000000 --disable-slither
    9
    // medusa fuzz
    10 2116×
    contract CryticTester is TargetFunctions, CryticAsserts {
    11
        constructor() payable {
    12
            setup();
    13
        }
    14
    }
    83% test/recon/Properties.sol
    Lines covered: 290 / 346 (83%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Asserts} from "@chimera/Asserts.sol";
    5
    import {MockERC20} from "@recon/MockERC20.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {ERC7540Properties} from "@properties-7540/ERC7540Properties.sol";
    8
    import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
    9
    10
    import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
    11
    12
    import {OpType} from "test/recon/BeforeAfter.sol";
    13
    import {BeforeAfter} from "./BeforeAfter.sol";
    14
    15
    abstract contract Properties is BeforeAfter, Asserts, ERC7540Properties {
    16
        using Math for uint256;
    17
        uint256 internal TOLERANCE = 10;
    18
    19
        // `!!!`-prefixed reasons are load-bearing for cross-fuzzer bug dedup in the analysis
    20
        // pipeline. Handler assertion failures surface natively on every fuzzer: as Panic(0x01)
    21
        // under echidna/medusa/recon and as recorded assertion failures under Foundry
    22
        // (`assertions_revert = false`, upstream handler-bug reporting).
    23
        string constant ASSERTION_REDEEM_MAX_REDEEM_SHOULD_NOT_REVERT =
    24
            "!!! redeeming maxRedeem should not revert";
    25
        string constant ASSERTION_WITHDRAW_MAX_WITHDRAW_SHOULD_NOT_REVERT =
    26
            "!!! withdraw of maxWithdraw should not revert";
    27
        string constant ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE =
    28
            "!!! previewDeposit and deposit equivalence";
    29
        string constant ASSERTION_PREVIEW_MINT_EQUIVALENCE =
    30
            "!!! previewMint and mint equivalence";
    31
        string constant ASSERTION_MINT_REDEEM_SYMMETRICAL =
    32
            "!!! user loses assets in mint/redeem flow";
    33
        string constant ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL =
    34
            "!!! user loses assets in deposit/withdraw flow";
    35
        string constant ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION =
    36
            "!!! maxRedeem should be reset to 0 after full redemption";
    37
        string constant ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL =
    38
            "!!! maxWithdraw should be reset to 0 after full withdrawal";
    39
        string constant ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS =
    40
            "!!! total shares redeemed must not exceed sum of requested shares";
    41
        string constant ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE =
    42
            "!!! Primary manager should always be changeable if not paused";
    43
        string constant ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED =
    44
            "!!! users should always be able to withdraw unless the system is paused";
    45
        string constant ASSERTION_CANCEL_REDEEM_PENDING_REQUEST_ZERO =
    46
            "!!! pendingRedeemRequests should be 0 after cancelling a redemption";
    47
        string constant ASSERTION_CANCEL_REDEEM_AVG_REQUEST_PPS_ZERO =
    48
            "!!! averageRequestPPS should be 0 after cancelling a redemption";
    49
        string constant ASSERTION_CANCEL_REDEEM_NO_OVERPAY =
    50
            "!!! user should not receive more than convertToAssets(pendingRedeemRequest) after cancelRedeem";
    51
        string constant ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION =
    52
            "!!! previewDeposit returns the correct amount compared to executing a deposit";
    53
        string constant ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION =
    54
            "!!! previewMint returns the correct amount compared to executing a mint";
    55
        string constant ASSERTION_TRANSFER_SHARES_CONSERVED =
    56
            "!!! shares are lost on transfer";
    57
        string constant ASSERTION_TRANSFER_COST_BASIS_CONSERVED =
    58
            "!!! cost basis is lost on transfer";
    59
        string constant ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM =
    60
            "!!! Claiming redemptions should never revert with INVALID_REDEEM_CLAIM";
    61
        string constant ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER =
    62
            "!!! _update should never revert in transfer";
    63
        string constant ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM =
    64
            "!!! _update should never revert in transferFrom";
    65
        string constant ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT =
    66
            "!!! strategy incurs loss on fulfillment";
    67
        string constant ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES =
    68
            "!!! previewMint and previewDeposit equivalence (from shares)";
    69
        string constant ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS =
    70
            "!!! previewMint and previewDeposit equivalence (from assets)";
    71
        string constant ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS =
    72
            "!!! previewMint is >= convertToAssets";
    73
        string constant ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT =
    74
            "!!! convertToShares is >= previewDepositShares (equivalent without fees)";
    75
        string constant ASSERTION_ERC7540_4_DEPOSIT =
    76
            "!!! ERC7540-4: deposit with more than max should revert";
    77
        string constant ASSERTION_ERC7540_4_MINT =
    78
            "!!! ERC7540-4: mint with more than max should revert";
    79
        string constant ASSERTION_ERC7540_4_WITHDRAW =
    80
            "!!! ERC7540-4: withdraw with more than max should revert";
    81
        string constant ASSERTION_ERC7540_4_REDEEM =
    82
            "!!! ERC7540-4: redeem with more than max should revert";
    83
        string constant ASSERTION_ERC7540_5 =
    84
            "!!! ERC7540-5: requestRedeem should revert if insufficient share balance";
    85
        string constant ASSERTION_ERC7540_7_WITHDRAW =
    86
            "!!! ERC7540-7: withdraw should not revert when amount <= max";
    87
        string constant ASSERTION_ERC7540_7_REDEEM =
    88
            "!!! ERC7540-7: redeem should not revert when amount <= max";
    89
        string constant ASSERTION_CANARY =
    90
            "!!! canary assertion";
    91
        string constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE =
    92
            "Canary invariant";
    93
    94
        /// @dev Property: oracle PPS doesn't change on deposit/mint/redeem/withdraw
    95 16×
        function invariant_oraclePPSDoesntChangeOnAddOrRemove() public returns (bool) {
    96 33×
            if (_currentOp == OpType.ADD || _currentOp == OpType.REMOVE) {
    97 20×
                eq(
    98
                    _before.oraclePPS,
    99
                    _after.oraclePPS,
    100
                    "deposit/withdrawal changes oracle PPS"
    101
                );
    102
            }
    103
            return true;
    104
        }
    105
    106
        /// @dev Property: naive PPS doesn't change on deposit/mint
    107
        // NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price
    108
        // function property_naivePPSDoesntChangeOnDepositOrMint() public {
    109
        //     if (
    110
        //         (_currentOp == OpType.ADD) && _before.naivePPS != 0 // price starts as zero when no shares minted
    111
        //     ) {
    112
        //         gte(
    113
        //             _after.naivePPS,
    114
        //             _before.naivePPS,
    115
        //             "deposit/mint cannot decrease naive PPS"
    116
        //         );
    117
        //     }
    118
        // }
    119
    120
        /// @dev Property: naive PPS doesn't change on redeem/withdraw
    121
        // NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price
    122
        // function property_naivePPSDoesntChangeOnRedeemOrWithdraw() public {
    123
        //     if (
    124
        //         (_currentOp == OpType.REMOVE) && _before.naivePPS != 0 // price starts as zero when no shares minted
    125
        //     ) {
    126
        //         gte(
    127
        //             _after.naivePPS,
    128
        //             _before.naivePPS,
    129
        //             "redeem/withdraw cannot decrease naive PPS"
    130
        //         );
    131
        //     }
    132
        // }
    133
    134
        /// @dev Property: fulfillRedeemRequest doesn't change naive PPS
    135
        // NOTE: removed because it's expected behavior that fulfillment burns shares but doesn't transfer assets to users so would change the naively calculated price
    136
        // function property_naivePPSDoesntChangeOnRedeem() public {
    137
        //     if (_currentOp == OpType.FULFILL) {
    138
        //         eq(
    139
        //             _before.naivePPS,
    140
        //             _after.naivePPS,
    141
        //             "fulfilling redemption changes naive PPS"
    142
        //         );
    143
        //     }
    144
        // }
    145
    146
        /// @dev Property: maxRedeem and maxWithdraw should always be equivalent
    147 21×
        function invariant_maxRedeemMaxWithdrawSymmetry() public returns (bool) {
    148 64×
            uint256 maxWithdraw = superVault.maxWithdraw(_getActor());
    149
            // convertToShares uses current price instead of avg from fulfillment
    150 64×
            uint256 withdrawPrice = superVaultStrategy.getAverageWithdrawPrice(
    151
                _getActor()
    152
            );
    153
            uint256 maxWithdrawAsShares = maxWithdraw.mulDiv(
    154 67×
                superVault.PRECISION(),
    155
                withdrawPrice,
    156
                Math.Rounding.Floor
    157
            );
    158
    159 0
            uint256 maxRedeem = superVault.maxRedeem(_getActor());
    160
    161
            eq(maxWithdrawAsShares, maxRedeem, "maxWithdrawAsShares != maxRedeem");
    162
            return true;
    163
        }
    164
    165
        /// @dev Property: requestRedeem should never reduce SuperVault shares
    166 16×
        function invariant_totalSharesDontDecreaseOnRedemptionRequest() public returns (bool) {
    167 15×
            if (_currentOp == OpType.REQUEST) {
    168 20×
                eq(
    169
                    _before.summedTotalShares,
    170
                    _after.summedTotalShares,
    171
                    "requestRedeem should never reduce SuperVault shares"
    172
                );
    173
            }
    174
            return true;
    175
        }
    176
    177
        /// @dev Property: `SuperVault::totalSupply` == SUM(user balances) + balanceOf(escrow)
    178 16×
        function invariant_shareSolvency() public returns (bool) {
    179
            uint256 sumOfShares = _sumTotalShares();
    180 88×
            eq(superVault.totalSupply(), sumOfShares, "vault shares are insolvent");
    181
            return true;
    182
        }
    183
    184
        /// @dev Property: balanceOf(escrow) >= SUM(controllers.pendingRedeemRequest)
    185 18×
        function invariant_escrowBalance() public returns (bool) {
    186
            address[] memory actors = _getActors();
    187
    188 67×
            uint256 escrowBalance = superVault.balanceOf(address(superVaultEscrow));
    189
            uint256 pendingActorShares;
    190 15×
            for (uint256 i; i < actors.length; i++) {
    191 96×
                pendingActorShares += superVault.pendingRedeemRequest(0, actors[i]);
    192
            }
    193
    194 21×
            gte(
    195
                escrowBalance,
    196
                pendingActorShares,
    197
                "balanceOf(escrow) < SUM(controllers.pendingRedeemRequest)"
    198
            );
    199
            return true;
    200
        }
    201
    202
        /// @dev Property: maxMint should be 0 when aggregator is paused
    203 21×
        function invariant_maxMintZeroWhenPaused() public returns (bool) {
    204 59×
            bool paused = superVaultAggregator.isStrategyPaused(
    205
                address(superVaultStrategy)
    206
            );
    207 66×
            uint256 maxMint = superVault.maxMint(_getActor());
    208
    209
            if (paused) {
    210 23×
                eq(maxMint, 0, "actor has nonzero maxMint when strategy is paused");
    211
            }
    212
            return true;
    213
        }
    214
    215
        /// @dev Property: maxDeposit should be 0 when strategy is paused
    216 19×
        function invariant_maxDepositZeroWhenPaused() public returns (bool) {
    217 59×
            bool paused = superVaultAggregator.isStrategyPaused(
    218
                address(superVaultStrategy)
    219
            );
    220 66×
            uint256 maxDeposit = superVault.maxDeposit(_getActor());
    221
    222
            if (paused) {
    223 20×
                eq(
    224
                    maxDeposit,
    225
                    0,
    226
                    "actor has nonzero maxDeposit when strategy is paused"
    227
                );
    228
            }
    229
            return true;
    230
        }
    231
    232
        /// @dev Property: SUM(accumulatorShares) doesn't change on SuperVault share transfers
    233 28×
        function invariant_accumulatorSharesSolvency() public returns (bool) {
    234 15×
            if (_currentOp == OpType.TRANSFER) {
    235 21×
                eq(
    236
                    _before.summedAccumulatorShares,
    237
                    _after.summedAccumulatorShares, // TODO: think about way to handle transfer on recipient
    238
                    "SUM(accumulatorShares) changed on SuperVault share transfers"
    239
                );
    240
            }
    241
            return true;
    242
        }
    243
    244
        /// @dev Property: SUM(accumulatorCostBasis) doesn't change on SuperVault share transfers
    245 16×
        function invariant_accumulatorCostBasisSolvency() public returns (bool) {
    246 15×
            if (_currentOp == OpType.TRANSFER) {
    247 20×
                eq(
    248
                    _before.summedAccumulatorCostBasis,
    249
                    _after.summedAccumulatorCostBasis,
    250
                    "SUM(accumulatorCostBasis) changed on SuperVault share transfers"
    251
                );
    252
            }
    253
            return true;
    254
        }
    255
    256
        /// @dev Property: cancelRedeem should never alter the supply of SuperVault tokens
    257 16×
        function invariant_cancelDoesntChangeTotalSupply() public returns (bool) {
    258 15×
            if (_currentOp == OpType.CANCEL) {
    259 20×
                eq(
    260
                    _before.summedTotalShares,
    261
                    _after.summedTotalShares,
    262
                    "cancelRedeem should never alter the supply of SuperVault tokens"
    263
                );
    264
            }
    265
            return true;
    266
        }
    267
    268
        /// @dev Property: if totalSupply > 0, then totalAssets > 0
    269 18×
        function invariant_assetBacking() public returns (bool) {
    270
            uint256 summedTotalAssets = _sumStrategyAssets();
    271
    272 72×
            if (superVault.totalSupply() > 0) {
    273 21×
                gt(
    274
                    summedTotalAssets,
    275
                    0,
    276
                    "if totalSupply > 0, then totalAssets > 0"
    277
                );
    278
            }
    279
            return true;
    280
        }
    281
    282
        /// @dev Property: SUM(shares) * PPS == totalAssets
    283 16×
        function invariant_totalAssets() public returns (bool) {
    284
            uint256 totalShares = _sumTotalShares();
    285 67×
            uint256 pps = superVaultAggregator.getPPS(address(superVaultStrategy));
    286 15×
            uint256 implicitTotalAssets = (totalShares * pps) /
    287 67×
                superVault.PRECISION();
    288
    289 70×
            uint256 vaultTotalAssets = superVault.totalAssets();
    290 20×
            eq(
    291
                implicitTotalAssets,
    292
                vaultTotalAssets,
    293
                "totalShares * pps != totalAssets"
    294
            );
    295
            return true;
    296
        }
    297
    298
        /// @dev Property: When a user requests a redemption and the PPS is >= the user PPS, user averageRequestPPS must not decrease
    299 19×
        function invariant_avgPPSDoesntDecrease() public returns (bool) {
    300
            uint256 currentPrice = _before.oraclePPS;
    301 22×
            uint256 beforeAvgPPS = _before.state[_getActor()].averageRequestPPS;
    302 20×
            uint256 afterAvgPPS = _after.state[_getActor()].averageRequestPPS;
    303
    304 26×
            if (_currentOp == OpType.REQUEST && currentPrice >= beforeAvgPPS) {
    305 20×
                gte(
    306
                    afterAvgPPS,
    307
                    beforeAvgPPS,
    308
                    "when a user requests a redemption and the PPS is >= the user PPS, user averageRequestPPS must not decrease"
    309
                );
    310
            }
    311
            return true;
    312
        }
    313
    314
        /// @dev Property: previewMint and previewDeposit equivalence (from shares)
    315 20×
        function global_previewEquivalenceFromShares_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES(uint256 shares) public {
    316 68×
            uint256 previewMintAssets = superVault.previewMint(shares);
    317 70×
            uint256 previewDepositShares = superVault.previewDeposit(
    318
                previewMintAssets
    319
            );
    320 69×
            uint256 price = superVaultStrategy.getStoredPPS();
    321
    322
            if (price > 0) {
    323
                eq(
    324
                    shares,
    325
                    previewDepositShares,
    326 17×
                    ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_SHARES
    327
                );
    328
            }
    329
        }
    330
    331
        /// @dev Property: previewMint and previewDeposit equivalence (from assets)
    332 20×
        function global_previewEquivalenceFromAssets_ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS(uint256 assets) public {
    333 68×
            uint256 previewDepositShares = superVault.previewDeposit(assets);
    334 63×
            uint256 previewMintAssets_under = superVault.previewMint(
    335
                previewDepositShares
    336
            );
    337 74×
            uint256 previewMintAssets_over = superVault.previewMint(
    338
                previewDepositShares + 1
    339
            );
    340 69×
            uint256 price = superVaultStrategy.getStoredPPS();
    341
    342
            if (price > 0) {
    343
                gte(
    344
                    assets,
    345
                    previewMintAssets_under,
    346 17×
                    ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS
    347
                );
    348
    349
                lte(
    350
                    assets,
    351
                    previewMintAssets_over,
    352 17×
                    ASSERTION_GLOBAL_PREVIEW_EQUIVALENCE_FROM_ASSETS
    353
                );
    354
            }
    355
        }
    356
    357
        /// @dev Property: previewMint is >= convertToAssets
    358 20×
        function global_comparePreviewMintAndConvertToAssets_ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS(
    359
            uint256 shares
    360
        ) public {
    361 68×
            uint256 previewMintAssets = superVault.previewMint(shares);
    362 63×
            uint256 convertToAssets = superVault.convertToAssets(shares);
    363
            gte(
    364
                previewMintAssets,
    365
                convertToAssets,
    366 17×
                ASSERTION_GLOBAL_PREVIEW_MINT_GTE_CONVERT_TO_ASSETS
    367
            );
    368
        }
    369
    370
        /// @dev Property: convertToShares is >= previewDepositShares (equivalent without fees)
    371 20×
        function global_comparePreviewDepositAndConvertToShares_ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT(
    372
            uint256 assets
    373
        ) public {
    374 68×
            uint256 previewDepositShares = superVault.previewDeposit(assets);
    375 70×
            uint256 convertToShares = superVault.convertToShares(assets);
    376
            gte(
    377
                convertToShares,
    378
                previewDepositShares,
    379 17×
                ASSERTION_GLOBAL_CONVERT_TO_SHARES_GTE_PREVIEW_DEPOSIT
    380
            );
    381
        }
    382
    383
        // NOTE: Withdrawal properties are more nuanced, as we should ensure that no gain can be had and that losses are imputed to the controller receiving the assets
    384
    385
        /// @dev Property: After all redemptions are processed, the sum of all claimable is <= balance available
    386 16×
        function invariant_sumOfClaimable() public returns (bool) {
    387
            address[] memory actors = _getActors();
    388
    389
            uint256 sumPending;
    390
            uint256 sumClaimable;
    391 14×
            for (uint256 i; i < actors.length; i++) {
    392 97×
                sumPending += superVault.pendingRedeemRequest(0, actors[i]);
    393 89×
                sumClaimable += superVault.maxWithdraw(actors[i]);
    394
            }
    395
    396 124×
            uint256 strategyBalance = MockERC20(superVault.asset()).balanceOf(
    397
                address(superVaultStrategy)
    398
            );
    399
    400
            // precondition: all pending has been fulfilled
    401
            if (sumPending == 0) {
    402 20×
                lte(
    403
                    sumClaimable,
    404
                    strategyBalance,
    405
                    "sum of all claimable is > balance available"
    406
                );
    407
            }
    408
            return true;
    409
        }
    410
    411
        /// @dev Property: If the sum of assets in SuperVaultStrategy and yield strategies is 0, maxWithdraw should be 0
    412 16×
        function invariant_sumOfAssetsMaxWithdrawable() public returns (bool) {
    413
            uint256 summedTotalAssets = _sumStrategyAssets();
    414
    415
            if (summedTotalAssets == 0) {
    416 66×
                uint256 maxWithdraw = superVault.maxWithdraw(_getActor());
    417 20×
                eq(
    418
                    maxWithdraw,
    419
                    0,
    420
                    "if sum of assets in SuperVaultStrategy and yield strategies is 0, maxWithdraw should be 0"
    421
                );
    422
            }
    423
            return true;
    424
        }
    425
    426
        /// @dev Property: averageWithdrawPrice should never decrease when new redemptions are fulfilled at a higher PPS
    427 16×
        function invariant_avgPPSMonotonicity() public returns (bool) {
    428
            if (
    429 23×
                _currentOp == OpType.FULFILL &&
    430 0
                _before.oraclePPS > _before.state[_getActor()].averageRequestPPS && // fulfilled at a higher price
    431 0
                _after.state[_getActor()].pendingRedeemRequest != 0 // redemptions have all been fulfilled/cancelled; avg gets reset to 0 in this case
    432
            ) {
    433 0
                gte(
    434 0
                    _after.state[_getActor()].averageRequestPPS,
    435 0
                    _before.state[_getActor()].averageRequestPPS,
    436
                    "averageWithdrawPrice should not decrease when fulfilled at a higher PPS"
    437
                );
    438
            }
    439
            return true;
    440
        }
    441
    442
        /// @dev Property: state.accumulatorShares >= superVaultState[controllers[i]].pendingRedeemRequest for each user
    443 18×
        function invariant_accumulatorSharesGtPendingRequests() public returns (bool) {
    444
            address[] memory actors = _getActors();
    445
    446 15×
            for (uint256 i; i < actors.length; i++) {
    447
                ISuperVaultStrategy.SuperVaultState
    448 82×
                    memory state = superVaultStrategy.getSuperVaultState(actors[i]);
    449 21×
                gte(
    450
                    state.accumulatorShares,
    451
                    state.pendingRedeemRequest,
    452
                    "state.accumulatorShares < state.pendingRedeemRequest"
    453
                );
    454
            }
    455
            return true;
    456
        }
    457
    458
        /// @dev Property: accumulatorShares is always accurately increased
    459 17×
        function invariant_accumulatorSharesIncrease() public returns (bool) {
    460 19×
            if (_currentOp == OpType.ADD) {
    461 22×
                uint256 accumulatorSharesBefore = _before
    462
                    .state[_getActor()]
    463
                    .accumulatorShares;
    464 20×
                uint256 accumulatorSharesAfter = _after
    465
                    .state[_getActor()]
    466
                    .accumulatorShares;
    467 20×
                uint256 actorSharesBefore = _before.superVaultShares[_getActor()];
    468 18×
                uint256 actorSharesAfter = _after.superVaultShares[_getActor()];
    469 21×
                eq(
    470
                    accumulatorSharesAfter - accumulatorSharesBefore,
    471
                    actorSharesAfter - actorSharesBefore,
    472
                    "accumulatorShares is always accurately updated"
    473
                );
    474
            }
    475
            return true;
    476
        }
    477
    478
        /// @dev Property: accumulatorCostBasis is always accurately increased
    479 16×
        function invariant_accumulatorCostBasisIncrease() public returns (bool) {
    480 15×
            if (_currentOp == OpType.ADD) {
    481 22×
                uint256 accumulatorCostBasisBefore = _before
    482
                    .state[_getActor()]
    483
                    .accumulatorCostBasis;
    484 19×
                uint256 accumulatorCostBasisAfter = _after
    485
                    .state[_getActor()]
    486
                    .accumulatorCostBasis;
    487
                eq(
    488
                    accumulatorCostBasisAfter - accumulatorCostBasisBefore,
    489
                    _after.strategyAssetBalance - _before.strategyAssetBalance,
    490
                    "accumulatorShares is always accurately updated"
    491
                );
    492
            }
    493
            return true;
    494
        }
    495
    496
        /// @dev Property: redemptions only burn the requested amount of shares (within tolerance range)
    497 16×
        function invariant_fulfillOnlyBurnsRequestedAmount() public returns (bool) {
    498
            uint256 TOLERANCE_CONSTANT = 10 wei; // taken from SuperVaultStrategy
    499
    500 15×
            if (_currentOp == OpType.FULFILL) {
    501 0
                uint256 pendingRedeemDelta = _before.summedPendingRedeem -
    502 0
                    _after.summedPendingRedeem;
    503 0
                uint256 totalSupplyDelta = _before.summedTotalShares -
    504 0
                    _after.summedTotalShares;
    505
    506
                // Check that burned amount is within tolerance of requested amount
    507 0
                if (totalSupplyDelta < pendingRedeemDelta) {
    508
                    // Burned less than requested - check within tolerance
    509 0
                    gte(
    510 0
                        totalSupplyDelta,
    511 0
                        pendingRedeemDelta - TOLERANCE_CONSTANT,
    512
                        "burned less than requested beyond tolerance"
    513
                    );
    514
                } else {
    515
                    // Burned more than requested - check within tolerance
    516 0
                    lte(
    517 0
                        totalSupplyDelta,
    518 0
                        pendingRedeemDelta + TOLERANCE_CONSTANT,
    519
                        "burned more than requested beyond tolerance"
    520
                    );
    521
                }
    522
            }
    523 0
            return true;
    524
        }
    525
    526
        /// @dev Property: accumulatorShares decreases by the exact amounts requested when fulfilling redemptions
    527 17×
        function invariant_accumulatorSharesDecreaseOnFulfill_exact() public returns (bool) {
    528 17×
            if (_currentOp == OpType.FULFILL) {
    529 0
                uint256 accumulatorSharesDelta = _before.summedAccumulatorShares -
    530 0
                    _after.summedAccumulatorShares;
    531 0
                uint256 totalSharesDelta = _before.summedTotalShares -
    532 0
                    _after.summedTotalShares;
    533
                eq(
    534 0
                    accumulatorSharesDelta,
    535 0
                    totalSharesDelta,
    536
                    "accumulatorShares decreases by the exact amounts requested when fulfilling redemptions"
    537
                );
    538
            }
    539
            return true;
    540
        }
    541
    542 15×
        function invariant_accumulatorSharesDecreaseOnFulfill_with_tolerance()
    543
            public returns (bool)
    544
        {
    545 15×
            if (_currentOp == OpType.FULFILL) {
    546 0
                uint256 accumulatorSharesDelta = _before.summedAccumulatorShares -
    547 0
                    _after.summedAccumulatorShares;
    548 0
                uint256 totalSharesDelta = _before.summedTotalShares -
    549 0
                    _after.summedTotalShares;
    550 0
                if (accumulatorSharesDelta >= totalSharesDelta) {
    551 0
                    lte(
    552 0
                        accumulatorSharesDelta - totalSharesDelta,
    553 0
                        TOLERANCE,
    554
                        "accumulatorShares decreases by more than TOLERANCE when fulfilling redemptions"
    555
                    );
    556
                } else {
    557 0
                    lte(
    558 0
                        totalSharesDelta - accumulatorSharesDelta,
    559 0
                        TOLERANCE,
    560
                        "accumulatorShares decreases by less than TOLERANCE when fulfilling redemptions"
    561
                    );
    562
                }
    563
            }
    564
            return true;
    565
        }
    566
    567
        /// Optimization Setters
    568
    569 21×
        function setpreviewAssetsGreater(uint256 shares) public {
    570 68×
            uint256 previewMintAssets = superVault.previewMint(shares);
    571 70×
            uint256 previewDepositAssets = superVault.previewDeposit(
    572
                previewMintAssets
    573
            );
    574
    575
            if (previewMintAssets > previewDepositAssets) {
    576
                previewMintAssetsGreater = int256(previewMintAssets);
    577
            } else {
    578
                previewDepositAssetsGreater = int256(previewDepositAssets);
    579
            }
    580
        }
    581
    582 24×
        function setPreviewSharesGreater(uint256 assets) public {
    583 68×
            uint256 previewDepositShares = superVault.previewDeposit(assets);
    584 63×
            uint256 previewMintShares = superVault.previewMint(
    585
                previewDepositShares
    586
            );
    587
    588
            if (previewDepositShares > previewMintShares) {
    589
                previewDepositSharesGreater =
    590
                    int256(previewDepositShares) -
    591
                    int256(previewMintShares);
    592
            } else {
    593
                previewMintSharesGreater =
    594
                    int256(previewMintShares) -
    595
                    int256(previewDepositShares);
    596
            }
    597
        }
    598
    599 15×
        function setFulfilledDifference() public {
    600 15×
            if (_currentOp == OpType.FULFILL) {
    601 0
                uint256 pendingRedeemDelta = _before.summedPendingRedeem -
    602 0
                    _after.summedPendingRedeem;
    603 0
                uint256 totalSupplyDelta = _before.summedTotalShares -
    604 0
                    _after.summedTotalShares;
    605
    606
                // Check that burned amount is within tolerance of requested amount
    607 0
                if (totalSupplyDelta < pendingRedeemDelta) {
    608
                    // Burned less than requested
    609 0
                    int256 burnedLessThanRequested = int256(pendingRedeemDelta) -
    610 0
                        int256(totalSupplyDelta);
    611
                } else {
    612
                    // Burned more than requested
    613 0
                    int256 burnedMoreThanRequested = int256(totalSupplyDelta) -
    614 0
                        int256(pendingRedeemDelta);
    615
                }
    616
            }
    617
        }
    618
    619
        /// Optimization Tests
    620
    621
        /// @dev Optimize the difference between the amount of assets in the system and claimable assets
    622 16×
        function optimize_maxDustAccumulation() public view returns (int256) {
    623
            address[] memory actors = _getActors();
    624
    625
            uint256 summedClaimableRedemptionsAsAssets;
    626 15×
            for (uint256 i; i < actors.length; i++) {
    627 74×
                uint256 claimableRedemptions = superVault.claimableRedeemRequest(
    628
                    0,
    629 17×
                    actors[i]
    630
                );
    631 64×
                summedClaimableRedemptionsAsAssets += superVault.convertToAssets(
    632
                    claimableRedemptions
    633
                );
    634
            }
    635
    636
            uint256 totalAssets = _sumStrategyAssets();
    637
    638
            return int256(totalAssets) - int256(summedClaimableRedemptionsAsAssets);
    639
        }
    640
    641
        /// @dev Optimize the difference between the amount of claimable assets and assets in the system
    642 19×
        function optimize_moreClaimableThanHeldDifference()
    643
            public
    644
            view
    645
            returns (int256)
    646
        {
    647
            address[] memory actors = _getActors();
    648
    649
            uint256 summedClaimableRedemptionsAsAssets;
    650 15×
            for (uint256 i; i < actors.length; i++) {
    651 74×
                summedClaimableRedemptionsAsAssets += superVault.maxWithdraw(
    652 17×
                    actors[i]
    653
                );
    654
            }
    655
    656
            uint256 totalAssets = _sumStrategyAssets();
    657
    658
            if (summedClaimableRedemptionsAsAssets > totalAssets) {
    659
                return
    660
                    int256(summedClaimableRedemptionsAsAssets) -
    661 0
                    int256(totalAssets);
    662
            }
    663
        }
    664
    665 12×
        function optimize_burnMoreThanRequestedInRedemption()
    666
            public
    667
            view
    668
            returns (int256)
    669
        {
    670
            return burnedMoreThanRequested;
    671
        }
    672
    673 14×
        function optimize_burnLessThanRequestedInRedemption()
    674
            public
    675
            view
    676
            returns (int256)
    677
        {
    678
            return burnedLessThanRequested;
    679
        }
    680
    681
        // function optimize_previewMintSharesGreater() public view returns (int256) {
    682
        //     return previewMintSharesGreater;
    683
        // }
    684
    685
        // function optimize_previewDepositSharesGreater()
    686
        //     public
    687
        //     view
    688
        //     returns (int256)
    689
        // {
    690
        //     return previewDepositSharesGreater;
    691
        // }
    692
    693
        // function optimize_previewMintAssetsGreater() public view returns (int256) {
    694
        //     return previewMintAssetsGreater;
    695
        // }
    696
    697
        // function optimize_previewDepositAssetsGreater()
    698
        //     public
    699
        //     view
    700
        //     returns (int256)
    701
        // {
    702
        //     return previewDepositAssetsGreater;
    703
        // }
    704
    705 20×
        function optimize_assetBackingDifference() public view returns (int256) {
    706
            uint256 summedTotalAssets = _sumStrategyAssets();
    707 69×
            uint256 totalSupply = superVault.totalSupply();
    708
    709 15×
            if (summedTotalAssets == 0 && totalSupply > 0) {
    710 0
                return int256(totalSupply);
    711
            }
    712
        }
    713
    714
        // Canaries
    715
    716
        /// @dev Canary assertion helper. A failing input is expected to be discovered during fuzzing.
    717 20×
        function assert_canary_ASSERTION_CANARY(uint256 entropy) public {
    718 22×
            t(entropy > 0, ASSERTION_CANARY);
    719
        }
    720
    721
        /// @dev Canary global invariant expected to fail immediately.
    722 16×
        function invariant_canary() public returns (bool) {
    723 20×
            t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE);
    724
            return true;
    725
        }
    726
    727
        // ERC7540 Properties from erc7540-reusable-properties
    728
    729
        /// @dev Property 7540-1: convertToAssets(totalSupply) == totalAssets unless price is 0.0
    730 16×
        function invariant_erc7540_1() public returns (bool) {
    731 12×
            actor = _getActor();
    732 21×
            t(
    733
                erc7540_1(address(superVault)),
    734
                "ERC7540-1: convertToAssets(totalSupply) == totalAssets failed"
    735
            );
    736
            return true;
    737
        }
    738
    739
        /// @dev Property 7540-2: convertToShares(totalAssets) == totalSupply unless price is 0.0
    740 16×
        function invariant_erc7540_2() public returns (bool) {
    741 12×
            actor = _getActor();
    742 21×
            t(
    743
                erc7540_2(address(superVault)),
    744
                "ERC7540-2: convertToShares(totalAssets) == totalSupply failed"
    745
            );
    746
            return true;
    747
        }
    748
    749
        /// @dev Property 7540-3: max* never reverts
    750 16×
        function invariant_erc7540_3() public returns (bool) {
    751 12×
            actor = _getActor();
    752 21×
            t(
    753
                erc7540_3(address(superVault)),
    754
                "ERC7540-3: max* functions should never revert"
    755
            );
    756
            return true;
    757
        }
    758
    759
        /// @dev Property 7540-4: claiming more than max always reverts
    760 20×
        function global_erc7540_4_deposit_ASSERTION_ERC7540_4_DEPOSIT(uint256 amt) public stateless {
    761 14×
            actor = _getActor();
    762
            t(
    763
                erc7540_4_deposit(address(superVault), amt),
    764 17×
                ASSERTION_ERC7540_4_DEPOSIT
    765
            );
    766
        }
    767
    768 20×
        function global_erc7540_4_mint_ASSERTION_ERC7540_4_MINT(uint256 amt) public stateless {
    769 14×
            actor = _getActor();
    770
            t(
    771
                erc7540_4_mint(address(superVault), amt),
    772 17×
                ASSERTION_ERC7540_4_MINT
    773
            );
    774
        }
    775
    776 20×
        function global_erc7540_4_withdraw_ASSERTION_ERC7540_4_WITHDRAW(uint256 amt) public stateless {
    777 14×
            actor = _getActor();
    778
            t(
    779
                erc7540_4_withdraw(address(superVault), amt),
    780 17×
                ASSERTION_ERC7540_4_WITHDRAW
    781
            );
    782
        }
    783
    784 20×
        function global_erc7540_4_redeem_ASSERTION_ERC7540_4_REDEEM(uint256 amt) public stateless {
    785 14×
            actor = _getActor();
    786
            t(
    787
                erc7540_4_redeem(address(superVault), amt),
    788 17×
                ASSERTION_ERC7540_4_REDEEM
    789
            );
    790
        }
    791
    792
        /// @dev Property 7540-5: requestRedeem reverts if the share balance is less than amount
    793 20×
        function global_erc7540_5_ASSERTION_ERC7540_5(uint256 shares) public stateless {
    794 14×
            actor = _getActor();
    795
            t(
    796 10×
                erc7540_5(address(superVault), address(superVault), shares),
    797 17×
                ASSERTION_ERC7540_5
    798
            );
    799
        }
    800
    801
        /// @dev Property 7540-6: preview* always reverts
    802
        // NOTE: previewMint and previewDeposit don't revert because deposits are sync
    803
        // function crytic_erc7540_6() public {
    804
        //     actor = _getActor();
    805
        //     t(
    806
        //         erc7540_6(address(superVault)),
    807
        //         "ERC7540-6: preview* functions should always revert"
    808
        //     );
    809
        // }
    810
    811
        /// @dev Property 7540-7: if max[method] > 0, then [method] (max) should not revert
    812
        // NOTE: maxDeposit always returns type(uint256).max
    813
        // function crytic_erc7540_7_deposit(uint256 amt) public {
    814
        //     actor = _getActor();
    815
        //     t(
    816
        //         erc7540_7_deposit(address(superVault), amt),
    817
        //         "ERC7540-7: deposit should not revert when amount <= max"
    818
        //     );
    819
        // }
    820
    821
        // NOTE: maxMint always returns type(uint256).max
    822
        // function crytic_erc7540_7_mint(uint256 amt) public stateless {
    823
        //     actor = _getActor();
    824
        //     t(
    825
        //         erc7540_7_mint(address(superVault), amt),
    826
        //         "ERC7540-7: mint should not revert when amount <= max"
    827
        //     );
    828
        // }
    829
    830 20×
        function global_erc7540_7_withdraw_ASSERTION_ERC7540_7_WITHDRAW(uint256 amt) public stateless {
    831 14×
            actor = _getActor();
    832
            t(
    833
                erc7540_7_withdraw(address(superVault), amt),
    834 17×
                ASSERTION_ERC7540_7_WITHDRAW
    835
            );
    836
        }
    837
    838
        // NOTE: this implements the check from ERC7540Properties directly because SuperVault logic implementation allows amt to be nonzero but round down to 0 when assets passed into redeem are calculated
    839 23×
        function global_erc7540_7_redeem_ASSERTION_ERC7540_7_REDEEM(uint256 amt) public stateless {
    840 20×
            actor = _getActor();
    841
    842 68×
            uint256 maxRedeem = superVault.maxRedeem(actor);
    843
            amt = between(amt, 0, maxRedeem);
    844
    845
            if (amt == 0) {
    846
                return; // Skip
    847
            }
    848
    849 0
            uint256 averageWithdrawPrice = superVaultStrategy
    850 0
                .getAverageWithdrawPrice(actor);
    851
    852
            // calculates assets in the same way as redeem to confirm that a nonzero amount is being requested
    853 0
            uint256 assets = amt.mulDiv(
    854 0
                averageWithdrawPrice,
    855 0
                superVault.PRECISION(),
    856 0
                Math.Rounding.Floor
    857
            );
    858
    859
            try superVault.redeem(amt, actor, actor) {} catch {
    860 0
                if (amt > 0 && assets > 0) {
    861 0
                    t(
    862 0
                        false,
    863 0
                        ASSERTION_ERC7540_7_REDEEM
    864
                    );
    865
                }
    866
            }
    867
        }
    868
    }
    99% test/recon/Setup.sol
    Lines covered: 158 / 159 (99%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    // External dependencies
    5
    import {BaseSetup} from "@chimera/BaseSetup.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {ActorManager} from "@recon/ActorManager.sol";
    8
    import {AssetManager} from "@recon/AssetManager.sol";
    9
    import {Utils} from "@recon/Utils.sol";
    10
    // ERC4626 Hooks
    11
    import {Deposit4626VaultHook} from "lib/v2-core/src/hooks/vaults/4626/Deposit4626VaultHook.sol";
    12
    import {ApproveAndDeposit4626VaultHook} from "lib/v2-core/src/hooks/vaults/4626/ApproveAndDeposit4626VaultHook.sol";
    13
    import {Redeem4626VaultHook} from "lib/v2-core/src/hooks/vaults/4626/Redeem4626VaultHook.sol";
    14
    15
    // ERC5115 Hooks
    16
    import {Deposit5115VaultHook} from "lib/v2-core/src/hooks/vaults/5115/Deposit5115VaultHook.sol";
    17
    import {ApproveAndDeposit5115VaultHook} from "lib/v2-core/src/hooks/vaults/5115/ApproveAndDeposit5115VaultHook.sol";
    18
    import {Redeem5115VaultHook} from "lib/v2-core/src/hooks/vaults/5115/Redeem5115VaultHook.sol";
    19
    20
    // ERC7540 Hooks
    21
    import {Deposit7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/Deposit7540VaultHook.sol";
    22
    import {Redeem7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/Redeem7540VaultHook.sol";
    23
    import {RequestDeposit7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/RequestDeposit7540VaultHook.sol";
    24
    import {RequestRedeem7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/RequestRedeem7540VaultHook.sol";
    25
    import {ApproveAndRequestDeposit7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/ApproveAndRequestDeposit7540VaultHook.sol";
    26
    import {CancelDepositRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/CancelDepositRequest7540Hook.sol";
    27
    import {CancelRedeemRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/CancelRedeemRequest7540Hook.sol";
    28
    import {ClaimCancelDepositRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/ClaimCancelDepositRequest7540Hook.sol";
    29
    import {ClaimCancelRedeemRequest7540Hook} from "lib/v2-core/src/hooks/vaults/7540/ClaimCancelRedeemRequest7540Hook.sol";
    30
    import {Withdraw7540VaultHook} from "lib/v2-core/src/hooks/vaults/7540/Withdraw7540VaultHook.sol";
    31
    32
    // Super Vault Hooks
    33
    import {CancelRedeemHook} from "lib/v2-core/src/hooks/vaults/super-vault/CancelRedeemHook.sol";
    34
    import {Withdraw7540VaultHook as SuperVaultWithdraw7540VaultHook} from "lib/v2-core/src/hooks/vaults/super-vault/Withdraw7540VaultHook.sol";
    35
    36
    // Source dependencies
    37
    import "src/SuperVault/SuperVault.sol";
    38
    import "src/SuperVault/SuperVaultAggregator.sol";
    39
    import "src/SuperVault/SuperVaultEscrow.sol";
    40
    import "src/SuperVault/SuperVaultStrategy.sol";
    41
    import "src/SuperGovernor.sol";
    42
    import {ISuperVaultAggregator} from "src/interfaces/SuperVault/ISuperVaultAggregator.sol";
    43
    import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
    44
    import {MockYieldSourceOracle} from "test/mocks/MockYieldSourceOracle.sol";
    45
    46
    // Test suite dependencies
    47
    import {YieldManager, YieldSourceType} from "test/recon/managers/YieldManager.sol";
    48
    import {MerkleTestHelper} from "test/recon/helpers/MerkleTestHelper.sol";
    49
    import {UnsafeSuperVaultAggregator} from "test/recon/helpers/UnsafeSuperVaultAggregator.sol";
    50
    import {MockERC4626YieldSourceOracle} from "test/recon/mocks/MockERC4626YieldSourceOracle.sol";
    51
    import {MockERC5115YieldSourceOracle} from "test/recon/mocks/MockERC5115YieldSourceOracle.sol";
    52
    import {MockERC7540YieldSourceOracle} from "test/recon/mocks/MockERC7540YieldSourceOracle.sol";
    53
    import {MockECDSAPPSOracle} from "test/recon/mocks/MockECDSAPPSOracle.sol";
    54
    55
    abstract contract Setup is
    56
        BaseSetup,
    57
        ActorManager,
    58
        AssetManager,
    59
        YieldManager,
    60
        Utils
    61
    {
    62
        // Configuration constants
    63
        uint8 internal constant DECIMALS = 18;
    64
        address asset;
    65
        address feeRecipient = address(0xbeef);
    66
    67
        // Core contracts
    68
        SuperGovernor superGovernor;
    69
        SuperVault superVault;
    70
        UnsafeSuperVaultAggregator superVaultAggregator;
    71
        SuperVaultEscrow superVaultEscrow;
    72
        SuperVaultStrategy superVaultStrategy;
    73
    74
        // Implementation contracts for aggregator
    75
        SuperVault vaultImpl;
    76
        SuperVaultStrategy strategyImpl;
    77
        SuperVaultEscrow escrowImpl;
    78
    79
        // Helpers
    80
        MerkleTestHelper merkleHelper;
    81
    82
        // ERC4626 Hooks
    83
        ApproveAndDeposit4626VaultHook approveAndDeposit4626Hook;
    84
        Deposit4626VaultHook deposit4626Hook;
    85
        Redeem4626VaultHook redeem4626Hook;
    86
    87
        // ERC5115 Hooks
    88
        ApproveAndDeposit5115VaultHook approveAndDeposit5115Hook;
    89
        Deposit5115VaultHook deposit5115Hook;
    90
        Redeem5115VaultHook redeem5115Hook;
    91
    92
        // ERC7540 Hooks
    93
        Deposit7540VaultHook deposit7540Hook;
    94
        Redeem7540VaultHook redeem7540Hook;
    95
        RequestDeposit7540VaultHook requestDeposit7540Hook;
    96
        RequestRedeem7540VaultHook requestRedeem7540Hook;
    97
        ApproveAndRequestDeposit7540VaultHook approveAndRequestDeposit7540Hook;
    98
        CancelDepositRequest7540Hook cancelDepositRequest7540Hook;
    99
        CancelRedeemRequest7540Hook cancelRedeemRequest7540Hook;
    100
        ClaimCancelDepositRequest7540Hook claimCancelDepositRequest7540Hook;
    101
        ClaimCancelRedeemRequest7540Hook claimCancelRedeemRequest7540Hook;
    102
        Withdraw7540VaultHook withdraw7540Hook;
    103
    104
        // Super Vault Hooks
    105
        CancelRedeemHook cancelRedeemHook;
    106
        SuperVaultWithdraw7540VaultHook superVaultWithdraw7540Hook;
    107
    108
        // Mocks
    109
        MockERC4626YieldSourceOracle erc4626YieldSourceOracle;
    110
        MockERC5115YieldSourceOracle erc5115YieldSourceOracle;
    111
        MockERC7540YieldSourceOracle erc7540YieldSourceOracle;
    112
        MockECDSAPPSOracle ECDSAPPSOracle;
    113
    114
        // Yield sources for different standards
    115
        address erc4626YieldSource;
    116
        address erc5115YieldSource;
    117
        address erc7540YieldSource;
    118
    119
        // Ghosts
    120
        bool hasUpdatedPPS;
    121
        int256 burnedMoreThanRequested;
    122
        int256 burnedLessThanRequested;
    123
        int256 previewMintSharesGreater; // setPreviewSharesGreater
    124
        int256 previewDepositSharesGreater; // setPreviewSharesGreater
    125
        int256 previewMintAssetsGreater; // setpreviewAssetsGreater
    126
        int256 previewDepositAssetsGreater; // setpreviewAssetsGreater
    127
    128
        // Canaries
    129
        bool executeHooksClampedSuccess;
    130
        bool executeHooksSuccess;
    131
        bool fulfillRedeemRequestsSuccess;
    132
        bool hasDeployedNewVault;
    133
    134
        /// === MODIFIERS === ///
    135
        /// Prank admin and actor
    136
    137
        modifier asAdmin() {
    138 632×
            vm.prank(address(this));
    139
            _;
    140
        }
    141
    142
        modifier asActor() {
    143 2519×
            vm.prank(address(_getActor()));
    144
            _;
    145
        }
    146
    147
        /// Makes a handler have no side effects
    148
        /// The fuzzer will call this anyway, and because it reverts it will be removed from shrinking
    149
        /// Replace the "withGhosts" with "stateless" to make the code clean
    150
        ///
    151
        /// Under Foundry with `assertions_revert = false` a failing t()/vm.assert* only records a
    152
        /// journaled write to the global fail slot, which the trailing revert would erase — making
    153
        /// every stateless assertion property invisible to the Foundry leg. Re-raise a failure
    154
        /// recorded during this call as Panic(0x01), which Foundry classifies as an assertion
    155
        /// failure; under echidna/medusa/recon a failing t() already reverted with Panic before
    156
        /// reaching this point, so the extra check never fires there.
    157
        modifier stateless() {
    158 117×
            bool preFailed = _foundryFailSlotSet();
    159
            _;
    160 55×
            if (!preFailed && _foundryFailSlotSet()) assert(false);
    161 15×
            revert("stateless");
    162
        }
    163
    164
        // bytes32("failed") — Foundry's GLOBAL_FAIL_SLOT on the cheatcode address, written
    165
        // (journaled sstore of 1) by non-reverting vm.assert* failures.
    166
        bytes32 internal constant _FOUNDRY_FAIL_SLOT =
    167
            0x6661696c65640000000000000000000000000000000000000000000000000000;
    168
    169
        /// Probes the Foundry fail slot with a raw staticcall so fuzzers without the `load`
    170
        /// cheatcode simply report "not set" instead of reverting.
    171
        function _foundryFailSlotSet() internal view returns (bool) {
    172 79×
            (bool ok, bytes memory ret) = address(vm).staticcall(
    173 70×
                abi.encodeWithSignature("load(address,bytes32)", address(vm), _FOUNDRY_FAIL_SLOT)
    174
            );
    175 47×
            return ok && ret.length >= 32 && abi.decode(ret, (bytes32)) != bytes32(0);
    176
        }
    177
    178
        /// === Setup === ///
    179
        /// This contains all calls to be performed in the tester constructor, both for Echidna and Foundry
    180
        function setup() internal virtual override {
    181
            // 1. Add additional actors
    182
            _addActor(address(0x100)); // Actor 1
    183
            _addActor(address(0x200)); // Actor 2
    184
    185
            // 2. Create assets using AssetManager
    186
            _newAsset(DECIMALS); // Deploy token with 18 decimals
    187
            _newAsset(DECIMALS); // UP token
    188
    189
            _switchAsset(0);
    190
    191
            // 3. Deploy all three types of yield sources using YieldManager
    192
            // Deploy ERC4626 yield source (default)
    193 18×
            erc4626YieldSource = _newYieldSource(
    194
                _getAsset(),
    195
                YieldSourceType.ERC4626
    196
            );
    197
    198
            // Deploy ERC5115 yield source
    199 18×
            erc5115YieldSource = _newYieldSource(
    200
                _getAsset(),
    201
                YieldSourceType.ERC5115
    202
            );
    203
    204
            // Deploy ERC7540 yield source
    205 18×
            erc7540YieldSource = _newYieldSource(
    206
                _getAsset(),
    207
                YieldSourceType.ERC7540
    208
            );
    209
    210
            // Set ERC4626 as the default active yield source
    211
            _switchYieldSource(0); // Switch to first yield source in the array (ERC4626)
    212
    213
            // 4. Deploy SuperGovernor first (required by other contracts)
    214 35×
            superGovernor = new SuperGovernor(
    215
                address(this), // superGovernor role
    216
                address(this), // governor role
    217
                address(this), // bankManager role
    218
                address(this), // gasManager role
    219 10×
                feeRecipient, // treasury
    220
                address(this) // prover
    221
            );
    222
    223
            // 5. Deploy implementation contracts for the aggregator
    224 32×
            vaultImpl = new SuperVault(address(superGovernor));
    225 41×
            strategyImpl = new SuperVaultStrategy(address(superGovernor));
    226 35×
            escrowImpl = new SuperVaultEscrow();
    227
    228
            // 6. Deploy SuperVaultAggregator with implementation contracts
    229 32×
            superVaultAggregator = new UnsafeSuperVaultAggregator(
    230
                address(superGovernor),
    231
                address(vaultImpl),
    232
                address(strategyImpl),
    233
                address(escrowImpl)
    234
            );
    235
    236
            // 7. Register the SuperVaultAggregator and UpToken address with SuperGovernor
    237 54×
            superGovernor.setAddress(
    238 59×
                superGovernor.SUPER_VAULT_AGGREGATOR(),
    239
                address(superVaultAggregator)
    240
            );
    241
    242 11×
            address[] memory assets = _getAssets();
    243 128×
            superGovernor.setAddress(superGovernor.UP(), assets[1]); // the second deployed token in the AssetManager is the UPToken
    244 114×
            superGovernor.setAddress(superGovernor.SUPER_BANK(), address(this));
    245
    246
            // 8. Deploy Mocks and Oracles
    247
    248
            // Deploy specific oracles for each yield source type
    249 36×
            erc4626YieldSourceOracle = new MockERC4626YieldSourceOracle();
    250 36×
            erc5115YieldSourceOracle = new MockERC5115YieldSourceOracle();
    251 36×
            erc7540YieldSourceOracle = new MockERC7540YieldSourceOracle();
    252 35×
            ECDSAPPSOracle = new MockECDSAPPSOracle();
    253
    254
            // ECDSAPPSOracle setup
    255 41×
            superGovernor.setActivePPSOracle(address(ECDSAPPSOracle));
    256 49×
            ECDSAPPSOracle.setSUPER_GOVERNORReturn(address(superVaultAggregator));
    257
    258
            // Set valid assets for all oracles
    259 21×
            asset = _getAsset();
    260 41×
            erc4626YieldSourceOracle.setValidAsset(asset, true);
    261 46×
            erc5115YieldSourceOracle.setValidAsset(asset, true);
    262 50×
            erc7540YieldSourceOracle.setValidAsset(asset, true);
    263
    264
            // 9. Create a vault trio using the aggregator
    265
            ISuperVaultAggregator.VaultCreationParams
    266 85×
                memory params = ISuperVaultAggregator.VaultCreationParams({
    267
                    asset: _getAsset(), // Use the token created by AssetManager
    268
                    name: "SuperVault",
    269
                    symbol: "SV",
    270
                    mainManager: address(this), // CONFIGURABLE: This parameter can be modified via target functions
    271 27×
                    secondaryManagers: new address[](0), // CONFIGURABLE: This parameter can be modified via target functions
    272
                    minUpdateInterval: 5, // CONFIGURABLE: This parameter can be modified via target functions
    273
                    maxStaleness: 300, // CONFIGURABLE: This parameter can be modified via target functions
    274 20×
                    feeConfig: ISuperVaultStrategy.FeeConfig({
    275
                        performanceFeeBps: 1000, // 10% performance fee
    276
                        managementFeeBps: 100, // 1% management fee
    277
                        recipient: feeRecipient
    278
                    })
    279
                });
    280
    281
            (
    282
                address vaultAddr,
    283
                address strategyAddr,
    284
                address escrowAddr
    285 62×
            ) = superVaultAggregator.createVault(params);
    286
    287
            // 10. Store the deployed contracts
    288 13×
            superVault = SuperVault(vaultAddr);
    289 12×
            superVaultStrategy = SuperVaultStrategy(payable(strategyAddr));
    290 16×
            superVaultEscrow = SuperVaultEscrow(escrowAddr);
    291
    292
            /// 11. Deploy all hook contracts and helper
    293 36×
            merkleHelper = new MerkleTestHelper();
    294
    295
            // Deploy ERC4626 Hooks
    296 36×
            approveAndDeposit4626Hook = new ApproveAndDeposit4626VaultHook();
    297 36×
            deposit4626Hook = new Deposit4626VaultHook();
    298 36×
            redeem4626Hook = new Redeem4626VaultHook();
    299
    300
            // Deploy ERC5115 Hooks
    301 36×
            approveAndDeposit5115Hook = new ApproveAndDeposit5115VaultHook();
    302 36×
            deposit5115Hook = new Deposit5115VaultHook();
    303 36×
            redeem5115Hook = new Redeem5115VaultHook();
    304
    305
            // Deploy ERC7540 Hooks
    306 36×
            deposit7540Hook = new Deposit7540VaultHook();
    307 36×
            redeem7540Hook = new Redeem7540VaultHook();
    308 36×
            requestDeposit7540Hook = new RequestDeposit7540VaultHook();
    309 36×
            requestRedeem7540Hook = new RequestRedeem7540VaultHook();
    310 36×
            approveAndRequestDeposit7540Hook = new ApproveAndRequestDeposit7540VaultHook();
    311 36×
            cancelDepositRequest7540Hook = new CancelDepositRequest7540Hook();
    312 36×
            cancelRedeemRequest7540Hook = new CancelRedeemRequest7540Hook();
    313 36×
            claimCancelDepositRequest7540Hook = new ClaimCancelDepositRequest7540Hook();
    314 36×
            claimCancelRedeemRequest7540Hook = new ClaimCancelRedeemRequest7540Hook();
    315 36×
            withdraw7540Hook = new Withdraw7540VaultHook();
    316
    317
            // Deploy Super Vault Hooks
    318 36×
            cancelRedeemHook = new CancelRedeemHook();
    319 32×
            superVaultWithdraw7540Hook = new SuperVaultWithdraw7540VaultHook();
    320
    321
            // Register all hooks with SuperGovernor
    322
            // ERC4626 Hooks (deposit hooks are regular hooks, redeem hooks are fulfill request hooks)
    323 45×
            superGovernor.registerHook(address(approveAndDeposit4626Hook), false);
    324 46×
            superGovernor.registerHook(address(deposit4626Hook), false);
    325 46×
            superGovernor.registerHook(address(redeem4626Hook), true); // fulfill request hook
    326
    327
            // ERC5115 Hooks
    328 46×
            superGovernor.registerHook(address(approveAndDeposit5115Hook), false);
    329 46×
            superGovernor.registerHook(address(deposit5115Hook), false);
    330 46×
            superGovernor.registerHook(address(redeem5115Hook), true); // fulfill request hook
    331
    332
            // ERC7540 Hooks (deposit/request hooks are regular hooks, redeem/withdraw hooks are fulfill request hooks)
    333 46×
            superGovernor.registerHook(address(deposit7540Hook), false);
    334 46×
            superGovernor.registerHook(address(redeem7540Hook), true); // fulfill request hook
    335 46×
            superGovernor.registerHook(address(requestDeposit7540Hook), false);
    336 46×
            superGovernor.registerHook(address(requestRedeem7540Hook), false);
    337 41×
            superGovernor.registerHook(
    338
                address(approveAndRequestDeposit7540Hook),
    339
                false
    340
            );
    341 41×
            superGovernor.registerHook(
    342
                address(cancelDepositRequest7540Hook),
    343
                false
    344
            );
    345 46×
            superGovernor.registerHook(address(cancelRedeemRequest7540Hook), false);
    346 41×
            superGovernor.registerHook(
    347
                address(claimCancelDepositRequest7540Hook),
    348
                false
    349
            );
    350 41×
            superGovernor.registerHook(
    351
                address(claimCancelRedeemRequest7540Hook),
    352
                false
    353
            );
    354 46×
            superGovernor.registerHook(address(withdraw7540Hook), true); // fulfill request hook
    355
    356
            // Super Vault Hooks
    357 48×
            superGovernor.registerHook(address(cancelRedeemHook), false);
    358 46×
            superGovernor.registerHook(address(superVaultWithdraw7540Hook), true); // fulfill request hook
    359
    360
            // 12. Set up approval array for contracts that need token access
    361 44×
            address[] memory approvalArray = new address[](6);
    362 24×
            approvalArray[0] = address(superVault);
    363 29×
            approvalArray[1] = address(superVaultStrategy);
    364 29×
            approvalArray[2] = address(superVaultAggregator);
    365 29×
            approvalArray[3] = erc4626YieldSource;
    366 29×
            approvalArray[4] = erc5115YieldSource;
    367 31×
            approvalArray[5] = erc7540YieldSource;
    368
    369
            // 13. Finalize asset deployment (mints to actors and sets approvals)
    370
            _finalizeAssetDeployment(_getActors(), approvalArray, type(uint88).max);
    371
        }
    372
    373
        /// Get hook addresses for different yield source types
    374
    375
        function _getApproveAndDepositHookForType(
    376
            YieldSourceType sourceType
    377
        ) internal view returns (address) {
    378 12×
            if (sourceType == YieldSourceType.ERC4626) {
    379
                return address(approveAndDeposit4626Hook);
    380 13×
            } else if (sourceType == YieldSourceType.ERC5115) {
    381
                return address(approveAndDeposit5115Hook);
    382 12×
            } else if (sourceType == YieldSourceType.ERC7540) {
    383
                return address(approveAndRequestDeposit7540Hook);
    384
            }
    385
            return address(0);
    386
        }
    387
    388
        function _getRedeemHookForType(
    389
            YieldSourceType sourceType
    390
        ) internal view returns (address) {
    391 24×
            if (sourceType == YieldSourceType.ERC4626) {
    392
                return address(redeem4626Hook);
    393 26×
            } else if (sourceType == YieldSourceType.ERC5115) {
    394
                return address(redeem5115Hook);
    395 24×
            } else if (sourceType == YieldSourceType.ERC7540) {
    396
                return address(redeem7540Hook);
    397
            }
    398 0
            return address(0);
    399
        }
    400
    401
        /// Get oracle addresses for different yield source types
    402
    403
        function _getYieldSourceOracleForType(
    404
            YieldSourceType sourceType
    405
        ) internal view returns (address) {
    406 12×
            if (sourceType == YieldSourceType.ERC4626) {
    407
                return address(erc4626YieldSourceOracle);
    408 13×
            } else if (sourceType == YieldSourceType.ERC5115) {
    409
                return address(erc5115YieldSourceOracle);
    410 13×
            } else if (sourceType == YieldSourceType.ERC7540) {
    411
                return address(erc7540YieldSourceOracle);
    412
            }
    413
        }
    414
    415
        /// @dev Helper function to determine yield source type from address
    416 18×
        function _getYieldSourceTypeFromAddress(
    417
            address yieldSource
    418
        ) internal view returns (YieldSourceType) {
    419
            // Get all available yield sources from YieldManager
    420 12×
            address[] memory yieldSources = _getYieldSources();
    421
    422
            // Check which index matches the current yield source
    423 28×
            for (uint256 i = 0; i < yieldSources.length; i++) {
    424 42×
                if (yieldSources[i] == yieldSource) {
    425
                    // Return the corresponding type based on creation order:
    426
                    // Index 0: ERC4626, Index 1: ERC5115, Index 2: ERC7540
    427 16×
                    if (i == 0) return YieldSourceType.ERC4626;
    428 16×
                    if (i == 1) return YieldSourceType.ERC5115;
    429 16×
                    if (i == 2) return YieldSourceType.ERC7540;
    430
                }
    431
            }
    432
    433
            // Default to ERC4626 if not found
    434
            return YieldSourceType.ERC4626;
    435
        }
    436
    437
        function _getERC4626YieldSourceOracle() internal view returns (address) {
    438
            return address(erc4626YieldSourceOracle);
    439
        }
    440
    441
        function _getERC5115YieldSourceOracle() internal view returns (address) {
    442
            return address(erc5115YieldSourceOracle);
    443
        }
    444
    445
        function _getERC7540YieldSourceOracle() internal view returns (address) {
    446
            return address(erc7540YieldSourceOracle);
    447
        }
    448
    449
        // Helpers
    450 35×
        function _getRandomActor(uint256 entropy) public view returns (address) {
    451 14×
            address[] memory actors = _getActors();
    452
    453 52×
            return actors[entropy % actors.length];
    454
        }
    455
    }
    73% test/recon/helpers/MerkleTestHelper.sol
    Lines covered: 25 / 34 (73%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
    5
    6
    /// @title MerkleTestHelper
    7
    /// @notice Helper contract for generating test Merkle roots and proofs for hook validation
    8 123×
    contract MerkleTestHelper {
    9
        /// @notice Generate a test Merkle root for ERC4626 deposit and redeem hooks
    10
        /// @param depositHook Address of the Deposit4626VaultHook or ApproveAndDeposit4626VaultHook contract
    11
        /// @param redeemHook Address of the Redeem4626VaultHook contract  
    12
        /// @param mockVault Address of the mock ERC4626 vault
    13
        /// @param mockToken Address of the mock token (unused but kept for compatibility)
    14
        /// @return root The Merkle root for the tree
    15
        /// @return proofs Array of proofs for each leaf [depositProof, redeemProof]
    16 28×
        function generateTestHooksRoot(
    17
            address depositHook,
    18
            address redeemHook,
    19
            address mockVault,
    20
            address mockToken
    21
        ) public pure returns (bytes32 root, bytes32[][] memory proofs) {
    22
            // Create leaves for the two hooks following the exact format from SuperVaultAggregator._createLeaf()
    23 25×
            bytes32[] memory leaves = new bytes32[](2);
    24
            
    25
            // Leaf 1: Deposit hook with yield source only (what inspect() returns)
    26
            // The inspect() function only returns abi.encodePacked(yieldSource)
    27 21×
            bytes memory depositArgs = abi.encodePacked(mockVault); // Only yield source address
    28 68×
            leaves[0] = keccak256(bytes.concat(keccak256(abi.encode(depositHook, depositArgs))));
    29
            
    30
            // Leaf 2: Redeem hook with yield source only (what inspect() returns)
    31
            // The inspect() function only returns abi.encodePacked(yieldSource)
    32 24×
            bytes memory redeemArgs = abi.encodePacked(mockVault); // Only yield source address
    33 72×
            leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs))));
    34
            
    35
            // Sort leaves to match standard Merkle tree ordering
    36 35×
            if (leaves[0] > leaves[1]) {
    37 67×
                (leaves[0], leaves[1]) = (leaves[1], leaves[0]);
    38
            }
    39
            
    40
            // For a 2-leaf tree, root is hash of the sorted leaves
    41 59×
            root = keccak256(abi.encodePacked(leaves[0], leaves[1]));
    42
            
    43
            // Store original leaf hashes for proof assignment
    44 56×
            bytes32 depositLeaf = keccak256(bytes.concat(keccak256(abi.encode(depositHook, depositArgs))));
    45 56×
            bytes32 redeemLeaf = keccak256(bytes.concat(keccak256(abi.encode(redeemHook, redeemArgs))));
    46
            
    47
            // Generate proofs for each leaf
    48 29×
            proofs = new bytes32[][](2);
    49 44×
            proofs[0] = new bytes32[](1); // Proof for deposit hook
    50 45×
            proofs[1] = new bytes32[](1); // Proof for redeem hook
    51
            
    52
            // In a 2-leaf tree, each leaf's proof is just its sibling
    53
            // Find which position each leaf ended up in after sorting
    54 23×
            if (leaves[0] == depositLeaf) {
    55
                // depositHook is first leaf after sorting
    56 48×
                proofs[0][0] = leaves[1]; // Sibling of deposit leaf
    57 48×
                proofs[1][0] = leaves[0]; // Sibling of redeem leaf  
    58
            } else {
    59
                // redeemHook is first leaf after sorting
    60 48×
                proofs[0][0] = leaves[1]; // Sibling of deposit leaf
    61 48×
                proofs[1][0] = leaves[0]; // Sibling of redeem leaf
    62
            }
    63
            
    64
            return (root, proofs);
    65
        }
    66
        
    67
        /// @notice Generate encoded hook arguments for Deposit4626VaultHook
    68
        /// @param yieldSource Address of the yield source vault
    69
        /// @param amount Amount to deposit
    70
        /// @param usePrevHookAmount Whether to use previous hook amount
    71
        /// @return Encoded hook arguments
    72 0
        function encodeDepositHookArgs(
    73
            address yieldSource,
    74
            uint256 amount,
    75
            bool usePrevHookAmount
    76 0
        ) public pure returns (bytes memory) {
    77 0
            return abi.encodePacked(
    78 0
                bytes32(0), // yieldSourceOracleId placeholder
    79
                yieldSource,
    80
                amount,
    81
                usePrevHookAmount
    82
            );
    83
        }
    84
        
    85
        /// @notice Generate encoded hook arguments for Redeem4626VaultHook
    86
        /// @param yieldSource Address of the yield source vault
    87
        /// @param owner Address of the owner
    88
        /// @param shares Number of shares to redeem
    89
        /// @param usePrevHookAmount Whether to use previous hook amount
    90
        /// @return Encoded hook arguments
    91
        function encodeRedeemHookArgs(
    92
            address yieldSource,
    93
            address owner,
    94
            uint256 shares,
    95
            bool usePrevHookAmount
    96 0
        ) public pure returns (bytes memory) {
    97 0
            return abi.encodePacked(
    98 0
                bytes32(0), // yieldSourceOracleId placeholder
    99
                yieldSource,
    100
                owner,
    101
                shares,
    102
                usePrevHookAmount
    103
            );
    104
        }
    105
        
    106
        /// @notice Generate encoded hook arguments for ApproveAndDeposit4626VaultHook
    107
        /// @param yieldSource Address of the yield source vault
    108
        /// @param token Address of the token to approve and deposit
    109
        /// @param amount Amount to deposit
    110
        /// @param usePrevHookAmount Whether to use previous hook amount
    111
        /// @return Encoded hook arguments
    112
        function encodeApproveAndDepositHookArgs(
    113
            address yieldSource,
    114
            address token,
    115
            uint256 amount,
    116
            bool usePrevHookAmount
    117
        ) public pure returns (bytes memory) {
    118
            return abi.encodePacked(
    119
                bytes32(0), // yieldSourceOracleId placeholder
    120
                yieldSource,
    121
                token,
    122
                amount,
    123
                usePrevHookAmount
    124
            );
    125
        }
    126
        
    127
        /// @notice Create a leaf hash for a specific hook and arguments
    128
        /// @param hookAddress Address of the hook contract
    129
        /// @param hookArgs Encoded hook arguments
    130
        /// @return leaf The leaf hash
    131 0
        function createLeaf(address hookAddress, bytes memory hookArgs) public pure returns (bytes32 leaf) {
    132 0
            return keccak256(bytes.concat(keccak256(abi.encode(hookAddress, hookArgs))));
    133
        }
    134
        
    135
        /// @notice Verify a Merkle proof against a root
    136
        /// @param proof Array of proof elements
    137
        /// @param root Merkle root
    138
        /// @param leaf Leaf to verify
    139
        /// @return True if proof is valid
    140
        function verifyProof(bytes32[] memory proof, bytes32 root, bytes32 leaf) public pure returns (bool) {
    141
            return MerkleProof.verify(proof, root, leaf);
    142
        }
    143
    }
    100% test/recon/helpers/UnsafeSuperVaultAggregator.sol
    Lines covered: 8 / 8 (100%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity 0.8.30;
    3
    4
    import {SuperVaultAggregator} from "src/SuperVault/SuperVaultAggregator.sol";
    5
    6
    /// @dev inherits from aggregator to override hook validation
    7 1173×
    contract UnsafeSuperVaultAggregator is SuperVaultAggregator {
    8 27×
        constructor(
    9
            address superGovernor_,
    10
            address vaultImpl_,
    11
            address strategyImpl_,
    12
            address escrowImpl_
    13
        )
    14
            SuperVaultAggregator(
    15
                superGovernor_,
    16
                vaultImpl_,
    17
                strategyImpl_,
    18
                escrowImpl_
    19
            )
    20
        {}
    21
    22 26×
        function validateHook(
    23
            address strategy,
    24
            ValidateHookArgs calldata args
    25
        ) external view override returns (bool isValid) {
    26
            return true;
    27
        }
    28
    }
    89% test/recon/managers/YieldManager.sol
    Lines covered: 26 / 29 (89%)
    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 "@recon/EnumerableSet.sol";
    8
    import {MockERC20} from "@recon/MockERC20.sol";
    9
    10
    import {MockERC4626Tester} from "../mocks/MockERC4626Tester.sol";
    11
    import {MockERC5115Tester} from "../mocks/MockERC5115Tester.sol";
    12
    import {MockERC7540Tester} from "../mocks/MockERC7540Tester.sol";
    13
    14
    /// @dev Source of truth for the yield sources being used in the test
    15
    /// @notice No yield sources should be used in the test suite without being added from here first
    16
    /// @notice Enum to specify the type of yield source to deploy
    17
    enum YieldSourceType {
    18
        ERC4626,
    19
        ERC5115,
    20
        ERC7540
    21
    }
    22
    23
    abstract contract YieldManager {
    24
        using EnumerableSet for EnumerableSet.AddressSet;
    25
    26
        /// @notice The current target for this set of variables
    27
        address private __yieldSource;
    28
    29
        /// @notice The current yield source type
    30
        YieldSourceType private __currentYieldSourceType;
    31
    32
        /// @notice The list of all yield sources being used
    33
        EnumerableSet.AddressSet private _yieldSources;
    34
    35
        // If the current target is address(0) then it has not been setup yet and should revert
    36
        error YieldSourceNotSetup();
    37
        // Do not allow duplicates
    38
        error YieldSourceExists();
    39
        // Enable only added yield sources
    40
        error YieldSourceNotAdded();
    41
        // Invalid yield source type
    42
        error InvalidYieldSourceType();
    43
    44
        /// @notice Returns the current active yield source
    45
        function _getYieldSource() internal view returns (address) {
    46 12×
            if (__yieldSource == address(0)) {
    47 0
                revert YieldSourceNotSetup();
    48
            }
    49
    50
            return __yieldSource;
    51
        }
    52
    53
        /// @notice Returns all yield sources being used
    54
        function _getYieldSources() internal view returns (address[] memory) {
    55
            return _yieldSources.values();
    56
        }
    57
    58
        /// @notice Returns the current yield source type
    59 21×
        function _getCurrentYieldSourceType() internal view returns (YieldSourceType) {
    60 180×
            return __currentYieldSourceType;
    61
        }
    62
    63
        /// @notice Creates a new yield source and adds it to the list of yield sources
    64
        /// @param asset The asset to create the yield source for
    65
        /// @param yieldSourceType The type of yield source to deploy
    66
        /// @return The address of the new yield source
    67 12×
        function _newYieldSource(address asset, YieldSourceType yieldSourceType) internal returns (address) {
    68
            address yieldSource_;
    69
            
    70 27×
            if (yieldSourceType == YieldSourceType.ERC4626) {
    71 52×
                yieldSource_ = address(new MockERC4626Tester(asset));
    72 13×
            } else if (yieldSourceType == YieldSourceType.ERC5115) {
    73
                yieldSource_ = address(new MockERC5115Tester(asset));
    74 14×
            } else if (yieldSourceType == YieldSourceType.ERC7540) {
    75
                yieldSource_ = address(new MockERC7540Tester(asset));
    76
            } else {
    77 0
                revert InvalidYieldSourceType();
    78
            }
    79
            
    80 10×
            _addYieldSource(yieldSource_);
    81 24×
            __yieldSource = yieldSource_; // sets the yield source as the current yield source
    82 32×
            __currentYieldSourceType = yieldSourceType;
    83
            return yieldSource_;
    84
        }
    85
    86
        /// @notice Creates a new yield source with ERC4626 by default (backward compatibility)
    87
        /// @param asset The asset to create the yield source for
    88
        /// @return The address of the new yield source
    89
        function _newYieldSource(address asset) internal returns (address) {
    90
            return _newYieldSource(asset, YieldSourceType.ERC4626);
    91
        }
    92
    93
        /// @notice Legacy function name for backward compatibility
    94
        /// @param asset The asset to create the yield source for
    95
        /// @return The address of the new yield source
    96
        function _newVault(address asset) internal returns (address) {
    97
            return _newYieldSource(asset, YieldSourceType.ERC4626);
    98
        }
    99
    100
        /// @notice Adds a yield source to the list of yield sources
    101
        /// @param target The address of the yield source to add
    102
        function _addYieldSource(address target) internal {
    103 20×
            if (_yieldSources.contains(target)) {
    104 0
                revert YieldSourceExists();
    105
            }
    106
    107 10×
            _yieldSources.add(target);
    108
        }
    109
    110
        /// @notice Legacy function name for backward compatibility
    111
        /// @param target The address of the yield source to add
    112
        function _addVault(address target) internal {
    113
            _addYieldSource(target);
    114
        }
    115
    116
        /// @notice Removes a yield source from the list of yield sources
    117
        /// @param target The address of the yield source to remove
    118
        function _removeYieldSource(address target) internal {
    119
            if (!_yieldSources.contains(target)) {
    120
                revert YieldSourceNotAdded();
    121
            }
    122
    123
            _yieldSources.remove(target);
    124
        }
    125
    126
        /// @notice Legacy function name for backward compatibility
    127
        /// @param target The address of the yield source to remove
    128
        function _removeVault(address target) internal {
    129
            _removeYieldSource(target);
    130
        }
    131
    132
        /// @notice Switches the current yield source based on the entropy
    133
        /// @param entropy The entropy to choose a random yield source in the array for switching
    134
        function _switchYieldSource(uint256 entropy) internal {
    135 36×
            address target = _yieldSources.at(entropy % _yieldSources.length());
    136 28×
            __yieldSource = target;
    137
        }
    138
    139
        /// @notice Legacy function name for backward compatibility
    140
        /// @param entropy The entropy to choose a random yield source in the array for switching
    141
        function _switchVault(uint256 entropy) internal {
    142
            _switchYieldSource(entropy);
    143
        }
    144
    145
        /// @notice Switches to a different yield source type by deploying a new yield source
    146
        /// @param asset The asset to create the new yield source for
    147
        /// @param newYieldSourceType The new yield source type to switch to
    148
        /// @return The address of the new yield source
    149
        function _switchYieldSourceType(address asset, YieldSourceType newYieldSourceType) internal returns (address) {
    150
            return _newYieldSource(asset, newYieldSourceType);
    151
        }
    152
    }
    30% test/recon/mocks/MockECDSAPPSOracle.sol
    Lines covered: 13 / 42 (30%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity ^0.8.0;
    3
    4
    import {ISuperVaultAggregator} from "src/interfaces/SuperVault/ISuperVaultAggregator.sol";
    5
    import {IECDSAPPSOracle} from "src/interfaces/oracles/IECDSAPPSOracle.sol";
    6
    7 177×
    contract MockECDSAPPSOracle {
    8
        //<>=============================================================<>
    9
        //||                                                             ||
    10
        //||                    NON-VIEW FUNCTIONS                       ||
    11
        //||                                                             ||
    12
        //<>=============================================================<>
    13
        // Mock implementation of updatePPS
    14 14×
        function updatePPS(IECDSAPPSOracle.UpdatePPSArgs memory args) public {
    15
            ISuperVaultAggregator.ForwardPPSArgs
    16 47×
                memory forwardArgs = ISuperVaultAggregator.ForwardPPSArgs({
    17
                    strategies: args.strategies,
    18
                    ppss: args.ppss,
    19
                    ppsStdevs: args.ppsStdevs,
    20
                    validatorSets: args.validatorSets,
    21
                    totalValidators: args.totalValidators,
    22
                    timestamps: args.timestamps,
    23
                    updateAuthority: msg.sender
    24
                });
    25
    26 53×
            ISuperVaultAggregator(_SUPER_GOVERNORReturn_0).forwardPPS(forwardArgs);
    27
        }
    28
    29
        //<>=============================================================<>
    30
        //||                                                             ||
    31
        //||                    SETTER FUNCTIONS                         ||
    32
        //||                                                             ||
    33
        //<>=============================================================<>
    34
        // Function to set return values for SUPER_GOVERNOR
    35 0
        function setSUPER_GOVERNORReturn(address _value0) public {
    36 0
            _SUPER_GOVERNORReturn_0 = _value0;
    37
        }
    38
    39
        // Function to set return values for UPDATE_PPS_TYPEHASH
    40 0
        function setUPDATE_PPS_TYPEHASHReturn(bytes32 _value0) public {
    41 0
            _UPDATE_PPS_TYPEHASHReturn_0 = _value0;
    42
        }
    43
    44
        // Function to set return values for domainSeparator
    45
        function setDomainSeparatorReturn(bytes32 _value0) public {
    46 0
            _domainSeparatorReturn_0 = _value0;
    47
        }
    48
    49
        /*******************************************************************
    50
         *   ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️ WARNING ⚠️  *
    51
         *-----------------------------------------------------------------*
    52
         *      Generally you only need to modify the sections above.      *
    53
         *          The code below handles system operations.              *
    54
         *******************************************************************/
    55
    56
        //<>=============================================================<>
    57
        //||                                                             ||
    58
        //||        ⚠️  STRUCT DEFINITIONS - DO NOT MODIFY  ⚠️          ||
    59
        //||                                                             ||
    60
        //<>=============================================================<>
    61
        // Struct definition removed - using IECDSAPPSOracle.UpdatePPSArgs from interface
    62
    63
        //<>=============================================================<>
    64
        //||                                                             ||
    65
        //||        ⚠️  EVENTS DEFINITIONS - DO NOT MODIFY  ⚠️          ||
    66
        //||                                                             ||
    67
        //<>=============================================================<>
    68
        event EIP712DomainChanged();
    69
        event PPSValidated(
    70
            address strategy,
    71
            uint256 pps,
    72
            uint256 ppsStdev,
    73
            uint256 validatorSet,
    74
            uint256 totalValidators,
    75
            uint256 timestamp,
    76
            address sender
    77
        );
    78
    79
        //<>=============================================================<>
    80
        //||                                                             ||
    81
        //||         ⚠️  INTERNAL STORAGE - DO NOT MODIFY  ⚠️           ||
    82
        //||                                                             ||
    83
        //<>=============================================================<>
    84
        address private _SUPER_GOVERNORReturn_0;
    85
        bytes32 private _UPDATE_PPS_TYPEHASHReturn_0;
    86
        bytes32 private _domainSeparatorReturn_0;
    87
        bytes1 private _eip712DomainReturn_0;
    88
        string private _eip712DomainReturn_1;
    89
        string private _eip712DomainReturn_2;
    90
        uint256 private _eip712DomainReturn_3;
    91
        address private _eip712DomainReturn_4;
    92
        bytes32 private _eip712DomainReturn_5;
    93
        uint256[] private _eip712DomainReturn_6;
    94
        uint256 private _nonceReturn_0;
    95
    96
        //<>=============================================================<>
    97
        //||                                                             ||
    98
        //||          ⚠️  VIEW FUNCTIONS - DO NOT MODIFY  ⚠️            ||
    99
        //||                                                             ||
    100
        //<>=============================================================<>
    101
        // Mock implementation of SUPER_GOVERNOR
    102 0
        function SUPER_GOVERNOR() public view returns (address) {
    103 0
            return _SUPER_GOVERNORReturn_0;
    104
        }
    105
    106
        // Mock implementation of UPDATE_PPS_TYPEHASH
    107 0
        function UPDATE_PPS_TYPEHASH() public view returns (bytes32) {
    108 0
            return _UPDATE_PPS_TYPEHASHReturn_0;
    109
        }
    110
    111
        // Mock implementation of domainSeparator
    112 0
        function domainSeparator() public view returns (bytes32) {
    113 0
            return _domainSeparatorReturn_0;
    114
        }
    115
    116
        // Mock implementation of eip712Domain
    117 0
        function eip712Domain()
    118
            public
    119
            view
    120
            returns (
    121 0
                bytes1,
    122 0
                string memory,
    123 0
                string memory,
    124 0
                uint256,
    125 0
                address,
    126 0
                bytes32,
    127 0
                uint256[] memory
    128
            )
    129
        {
    130 0
            return (
    131 0
                _eip712DomainReturn_0,
    132 0
                _eip712DomainReturn_1,
    133 0
                _eip712DomainReturn_2,
    134 0
                _eip712DomainReturn_3,
    135 0
                _eip712DomainReturn_4,
    136 0
                _eip712DomainReturn_5,
    137 0
                _eip712DomainReturn_6
    138
            );
    139
        }
    140
    141
        // Mock implementation of nonce
    142 0
        function nonce() public view returns (uint256) {
    143 0
            return _nonceReturn_0;
    144
        }
    145
    }
    91% test/recon/mocks/MockERC4626Tester.sol
    Lines covered: 107 / 117 (91%)
    1
    // SPDX-License-Identifier: MIT
    2
    // Heavily inspired by https://github.com/liquity/V2-gov/blob/9632de9a988522775336d9b60cdf2542efc600db/test/mocks/MaliciousInitiative.sol
    3
    pragma solidity ^0.8.0;
    4
    5
    import {MockERC20} from "@recon/MockERC20.sol";
    6
    7
    abstract contract ERC4626 is MockERC20 {
    8 18×
        MockERC20 public immutable asset;
    9
    10
        event Deposit(
    11
            address indexed caller,
    12
            address indexed owner,
    13
            uint256 assets,
    14
            uint256 shares
    15
        );
    16
        event Withdraw(
    17
            address indexed caller,
    18
            address indexed receiver,
    19
            address indexed owner,
    20
            uint256 assets,
    21
            uint256 shares
    22
        );
    23
    24
        constructor(MockERC20 _asset) MockERC20("MockERC4626Tester", "MCT", 18) {
    25
            asset = _asset;
    26
        }
    27
    28
        function deposit(
    29
            uint256 assets,
    30
            address receiver
    31
        ) public virtual returns (uint256) {
    32 24×
            uint256 shares = previewDeposit(assets);
    33 21×
            _deposit(msg.sender, receiver, assets, shares);
    34
            return shares;
    35
        }
    36
    37
        function mint(
    38
            uint256 shares,
    39
            address receiver
    40
        ) public virtual returns (uint256) {
    41 16×
            uint256 assets = previewMint(shares);
    42 14×
            _deposit(msg.sender, receiver, assets, shares);
    43
            return assets;
    44
        }
    45
    46
        function withdraw(
    47
            uint256 assets,
    48
            address receiver,
    49
            address owner
    50
        ) public virtual returns (uint256) {
    51
            uint256 shares = previewWithdraw(assets);
    52
            _withdraw(msg.sender, receiver, owner, assets, shares);
    53
            return shares;
    54
        }
    55
    56
        function redeem(
    57
            uint256 shares,
    58
            address receiver,
    59
            address owner
    60
        ) public virtual returns (uint256) {
    61
            uint256 assets = previewRedeem(shares);
    62
            _withdraw(msg.sender, receiver, owner, assets, shares);
    63
            return assets;
    64
        }
    65
    66 80×
        function totalAssets() public view virtual returns (uint256) {
    67 295×
            return asset.balanceOf(address(this));
    68
        }
    69
    70 25×
        function convertToShares(
    71
            uint256 assets
    72 10×
        ) public view virtual returns (uint256) {
    73 10×
            uint256 supply = totalSupply;
    74 65×
            return supply == 0 ? assets : (assets * supply) / totalAssets();
    75
        }
    76
    77 14×
        function convertToAssets(
    78
            uint256 shares
    79
        ) public view virtual returns (uint256) {
    80
            uint256 supply = totalSupply;
    81 121×
            return supply == 0 ? shares : (shares * totalAssets()) / supply;
    82
        }
    83
    84
        function previewDeposit(
    85
            uint256 assets
    86
        ) public view virtual returns (uint256) {
    87 20×
            return convertToShares(assets);
    88
        }
    89
    90
        function previewMint(uint256 shares) public view virtual returns (uint256) {
    91
            uint256 supply = totalSupply;
    92
            return supply == 0 ? shares : (shares * totalAssets()) / supply;
    93
        }
    94
    95
        function previewWithdraw(
    96
            uint256 assets
    97
        ) public view virtual returns (uint256) {
    98
            uint256 supply = totalSupply;
    99
            return supply == 0 ? assets : (assets * supply) / totalAssets();
    100
        }
    101
    102
        function previewRedeem(
    103
            uint256 shares
    104
        ) public view virtual returns (uint256) {
    105 16×
            return convertToAssets(shares);
    106
        }
    107
    108
        function maxDeposit(address) public view virtual returns (uint256) {
    109
            return type(uint256).max;
    110
        }
    111
    112
        function maxMint(address) public view virtual returns (uint256) {
    113
            return type(uint256).max;
    114
        }
    115
    116 12×
        function maxWithdraw(address owner) public view virtual returns (uint256) {
    117 16×
            return convertToAssets(balanceOf[owner]);
    118
        }
    119
    120 10×
        function maxRedeem(address owner) public view virtual returns (uint256) {
    121 12×
            return balanceOf[owner];
    122
        }
    123
    124 18×
        function _deposit(
    125
            address caller,
    126
            address receiver,
    127
            uint256 assets,
    128
            uint256 shares
    129
        ) internal virtual {
    130 191×
            asset.transferFrom(caller, address(this), assets);
    131 18×
            _mint(receiver, shares);
    132 63×
            emit Deposit(caller, receiver, assets, shares);
    133
        }
    134
    135 22×
        function _withdraw(
    136
            address caller,
    137
            address receiver,
    138
            address owner,
    139
            uint256 assets,
    140
            uint256 shares
    141
        ) internal virtual {
    142 32×
            if (caller != owner) {
    143 80×
                allowance[owner][caller] -= shares;
    144
            }
    145 23×
            _burn(owner, shares);
    146 188×
            asset.transfer(receiver, assets);
    147 69×
            emit Withdraw(caller, receiver, owner, assets, shares);
    148
        }
    149
    }
    150
    151
    enum FunctionType {
    152
        NONE,
    153
        DEPOSIT,
    154
        MINT,
    155
        WITHDRAW,
    156
        REDEEM
    157
    }
    158
    159
    enum RevertType {
    160
        NONE,
    161
        THROW,
    162
        OOG,
    163
        RETURN_BOMB,
    164
        REVERT_BOMB
    165
    }
    166
    167
    /// @dev This will use the simplest possible implementation initially to allow getting coverage and will be expanded on as necessary for testing potentially more interesting behaviors
    168
    /// @dev Note that blindspots not testable with this current implementation are covered in the ERC4626-integrations.md file
    169 1098×
    contract MockERC4626Tester is ERC4626 {
    170 0
        mapping(FunctionType => RevertType) public revertBehaviours;
    171
    172 0
        uint8 public decimalsOffset;
    173
        /// @dev Track total losses
    174 0
        uint256 public totalLosses;
    175 0
        uint256 public totalGains;
    176 0
        uint256 public lossOnWithdraw;
    177
        uint256 public MAX_BPS = 10_000;
    178
    179 28×
        constructor(address _asset) ERC4626(MockERC20(_asset)) {}
    180
    181
        /// Standard ERC4626 functions ///
    182
    183
        /// @dev Deposit assets, reverts as specified
    184 33×
        function deposit(
    185
            uint256 assets,
    186
            address receiver
    187
        ) public override returns (uint256) {
    188 18×
            _performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]);
    189 15×
            return super.deposit(assets, receiver);
    190
        }
    191
    192
        /// @dev Mint shares, reverts as specified
    193 22×
        function mint(
    194
            uint256 shares,
    195
            address receiver
    196
        ) public override returns (uint256) {
    197 12×
            _performRevertBehaviour(revertBehaviours[FunctionType.MINT]);
    198 10×
            return super.mint(shares, receiver);
    199
        }
    200
    201
        /// @dev Withdraw assets, reverts as specified
    202 26×
        function withdraw(
    203
            uint256 assets,
    204
            address receiver,
    205
            address owner
    206
        ) public override returns (uint256) {
    207 12×
            _performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]);
    208
    209 16×
            uint256 shares = previewWithdraw(assets);
    210 52×
            uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS);
    211 18×
            _withdraw(msg.sender, receiver, owner, lossyAssets, shares);
    212
    213
            return shares;
    214
        }
    215
    216
        /// @dev Redeem shares, reverts as specified
    217 50×
        function redeem(
    218
            uint256 shares,
    219
            address receiver,
    220
            address owner
    221
        ) public override returns (uint256) {
    222 24×
            _performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]);
    223
    224 32×
            uint256 assets = previewRedeem(shares);
    225 104×
            uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS);
    226 35×
            _withdraw(msg.sender, receiver, owner, lossyAssets, shares);
    227
    228
            return lossyAssets;
    229
        }
    230
    231
        /// @dev Preview deposit, reverts as specified
    232 35×
        function previewDeposit(
    233
            uint256 assets
    234 10×
        ) public view override returns (uint256) {
    235 30×
            _performRevertBehaviour(revertBehaviours[FunctionType.DEPOSIT]);
    236 20×
            return super.previewDeposit(assets);
    237
        }
    238
    239
        /// @dev Preview mint, reverts as specified
    240 12×
        function previewMint(
    241
            uint256 shares
    242
        ) public view override returns (uint256) {
    243 12×
            _performRevertBehaviour(revertBehaviours[FunctionType.MINT]);
    244
            return super.previewMint(shares);
    245
        }
    246
    247
        /// @dev Preview withdraw, reverts as specified
    248
        function previewWithdraw(
    249
            uint256 assets
    250
        ) public view override returns (uint256) {
    251 133×
            _performRevertBehaviour(revertBehaviours[FunctionType.WITHDRAW]);
    252
            return super.previewWithdraw(assets);
    253
        }
    254
    255
        /// @dev Preview redeem, reverts as specified
    256
        function previewRedeem(
    257
            uint256 shares
    258
        ) public view override returns (uint256) {
    259 24×
            _performRevertBehaviour(revertBehaviours[FunctionType.REDEEM]);
    260 16×
            return super.previewRedeem(shares);
    261
        }
    262
    263
        /// @dev Revert in different ways to test the revert behaviour
    264 15×
        function _performRevertBehaviour(RevertType action) internal pure {
    265 65×
            if (action == RevertType.THROW) {
    266 16×
                revert("A normal Revert");
    267
            }
    268
    269
            // 3 gas per iteration, consider changing to storage changes if traces are cluttered
    270 65×
            if (action == RevertType.OOG) {
    271 0
                uint256 i;
    272 0
                while (true) {
    273 0
                    ++i;
    274
                }
    275
            }
    276
    277 65×
            if (action == RevertType.RETURN_BOMB) {
    278 0
                uint256 _bytes = 2_000_000;
    279
                assembly {
    280 0
                    return(0, _bytes)
    281
                }
    282
            }
    283
    284 65×
            if (action == RevertType.REVERT_BOMB) {
    285
                uint256 _bytes = 2_000_000;
    286
                assembly {
    287
                    revert(0, _bytes)
    288
                }
    289
            }
    290
    291
            return; // NONE
    292
        }
    293
    294
        /// Custom functions for testing vault behavior ///
    295
    296
        /// @dev Specify the revert behavior on each function
    297 14×
        function setRevertBehavior(FunctionType ft, RevertType rt) public {
    298 47×
            revertBehaviours[ft] = rt;
    299
        }
    300
    301
        /// @dev Simulate a loss on the vault's assets
    302 11×
        function simulateLoss(uint256 lossAmount) external {
    303 66×
            MockERC20(asset).transfer(address(0xbeef), lossAmount);
    304 11×
            totalLosses += lossAmount;
    305
        }
    306
    307
        /// @dev Simulate a gain on the vault's assets (similar to Yearn's profit taking)
    308 12×
        function simulateGain(uint256 gainAmount) external {
    309 67×
            MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount);
    310 15×
            totalGains += gainAmount;
    311
        }
    312
    313
        /// @dev Set the loss on withdraw as percentage of the assets being withdrawn
    314 16×
        function setLossOnWithdraw(uint256 _lossOnWithdraw) public {
    315 14×
            _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100%
    316
            lossOnWithdraw = _lossOnWithdraw;
    317
        }
    318
    319
        /// @dev Set the decimal offset. Only possible with no supply.
    320 12×
        function setDecimalsOffset(uint8 targetDecimalsOffset) external {
    321
            if (totalSupply != 0) {
    322
                revert("Supply is not zero");
    323
            }
    324 15×
            decimalsOffset = targetDecimalsOffset;
    325
        }
    326
    }
    10% test/recon/mocks/MockERC4626YieldSourceOracle.sol
    Lines covered: 6 / 57 (10%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.30;
    3
    4
    import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    5
    import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
    6
    7
    /// @title MockERC4626YieldSourceOracle
    8
    /// @notice Mock oracle for ERC4626 vaults in testing
    9 278×
    contract MockERC4626YieldSourceOracle is IYieldSourceOracle {
    10 0
        mapping(address => bool) public validAssetMap;
    11
        
    12 11×
        function setValidAsset(address asset, bool isValid) external {
    13 27×
            validAssetMap[asset] = isValid;
    14
        }
    15
        
    16 0
        function decimals(address yieldSourceAddress) external view returns (uint8) {
    17 0
            return IERC4626(yieldSourceAddress).decimals();
    18
        }
    19
        
    20 48×
        function getShareOutput(
    21
            address yieldSourceAddress,
    22
            address,
    23
            uint256 assetsIn
    24
        ) external view returns (uint256) {
    25 130×
            return IERC4626(yieldSourceAddress).previewDeposit(assetsIn);
    26
        }
    27
        
    28 0
        function getAssetOutput(
    29
            address yieldSourceAddress,
    30
            address,
    31
            uint256 sharesIn
    32 0
        ) public view returns (uint256) {
    33 0
            return IERC4626(yieldSourceAddress).previewRedeem(sharesIn);
    34
        }
    35
        
    36 0
        function getPricePerShare(address yieldSourceAddress) external view returns (uint256) {
    37 0
            IERC4626 yieldSource = IERC4626(yieldSourceAddress);
    38 0
            uint256 _decimals = yieldSource.decimals();
    39 0
            return yieldSource.convertToAssets(10 ** _decimals);
    40
        }
    41
        
    42 0
        function getBalanceOfOwner(
    43
            address yieldSourceAddress,
    44
            address ownerOfShares
    45 0
        ) external view returns (uint256) {
    46 0
            return IERC4626(yieldSourceAddress).balanceOf(ownerOfShares);
    47
        }
    48
        
    49 0
        function getTVLByOwnerOfShares(
    50
            address yieldSourceAddress,
    51
            address ownerOfShares
    52 0
        ) external view returns (uint256) {
    53 0
            uint256 shares = IERC4626(yieldSourceAddress).balanceOf(ownerOfShares);
    54 0
            return IERC4626(yieldSourceAddress).convertToAssets(shares);
    55
        }
    56
        
    57 0
        function getTVL(address yieldSourceAddress) external view returns (uint256) {
    58 0
            return IERC4626(yieldSourceAddress).totalAssets();
    59
        }
    60
        
    61 0
        function getPricePerShareMultiple(
    62
            address[] memory yieldSourceAddresses
    63 0
        ) external view returns (uint256[] memory) {
    64 0
            uint256[] memory prices = new uint256[](yieldSourceAddresses.length);
    65 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    66 0
                IERC4626 yieldSource = IERC4626(yieldSourceAddresses[i]);
    67 0
                uint256 _decimals = yieldSource.decimals();
    68 0
                prices[i] = yieldSource.convertToAssets(10 ** _decimals);
    69
            }
    70 0
            return prices;
    71
        }
    72
        
    73 0
        function getTVLByOwnerOfSharesMultiple(
    74
            address[] memory yieldSourceAddresses,
    75
            address[][] memory ownersOfShares
    76 0
        ) external view returns (uint256[][] memory) {
    77 0
            uint256[][] memory result = new uint256[][](yieldSourceAddresses.length);
    78 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    79 0
                result[i] = new uint256[](ownersOfShares[i].length);
    80 0
                for (uint256 j = 0; j < ownersOfShares[i].length; j++) {
    81 0
                    uint256 shares = IERC4626(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]);
    82 0
                    result[i][j] = IERC4626(yieldSourceAddresses[i]).convertToAssets(shares);
    83
                }
    84
            }
    85 0
            return result;
    86
        }
    87
        
    88 0
        function getTVLMultiple(
    89
            address[] memory yieldSourceAddresses
    90 0
        ) external view returns (uint256[] memory) {
    91 0
            uint256[] memory tvls = new uint256[](yieldSourceAddresses.length);
    92 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    93 0
                tvls[i] = IERC4626(yieldSourceAddresses[i]).totalAssets();
    94
            }
    95
            return tvls;
    96
        }
    97
        
    98 0
        function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
    99 0
            return validAssetMap[asset];
    100
        }
    101
        
    102 0
        function isValidUnderlyingAssets(
    103
            address[] memory,
    104
            address[] memory assets
    105 0
        ) external view returns (bool[] memory) {
    106 0
            bool[] memory validities = new bool[](assets.length);
    107 0
            for (uint256 i = 0; i < assets.length; i++) {
    108 0
                validities[i] = validAssetMap[assets[i]];
    109
            }
    110
            return validities;
    111
        }
    112
        
    113 0
        function getAssetOutputWithFees(
    114
            bytes32,
    115
            address yieldSourceAddress,
    116
            address,
    117
            address,
    118
            uint256 sharesIn
    119 0
        ) external view returns (uint256) {
    120 0
            return IERC4626(yieldSourceAddress).previewRedeem(sharesIn);
    121
        }
    122
    }
    25% test/recon/mocks/MockERC5115Tester.sol
    Lines covered: 26 / 104 (25%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.0;
    3
    4
    import {MockERC20} from "@recon/MockERC20.sol";
    5
    6
    abstract contract ERC5115 is MockERC20 {
    7 0
        MockERC20 public immutable yieldToken;
    8 0
        address[] public tokensIn;
    9 0
        address[] public tokensOut;
    10
    11
        event Deposit(
    12
            address indexed caller,
    13
            address indexed receiver,
    14
            address indexed tokenIn,
    15
            uint256 amountDeposited,
    16
            uint256 amountSyOut
    17
        );
    18
    19
        event Redeem(
    20
            address indexed caller,
    21
            address indexed receiver,
    22
            address indexed tokenOut,
    23
            uint256 amountSyToRedeem,
    24
            uint256 amountTokenOut
    25
        );
    26
    27
        constructor(
    28
            MockERC20 _yieldToken
    29
        ) MockERC20("MockERC5115Tester", "SY5115", 18) {
    30
            yieldToken = _yieldToken;
    31 22×
            tokensIn.push(address(_yieldToken));
    32 23×
            tokensOut.push(address(_yieldToken));
    33
        }
    34
    35 0
        function deposit(
    36
            address receiver,
    37
            address tokenIn,
    38
            uint256 amountTokenToDeposit,
    39
            uint256 minSharesOut,
    40
            bool depositFromInternalBalance
    41 0
        ) public virtual returns (uint256 amountSharesOut) {
    42 0
            amountSharesOut = previewDeposit(tokenIn, amountTokenToDeposit);
    43
    44 0
            MockERC20(tokenIn).transferFrom(
    45 0
                msg.sender,
    46 0
                address(this),
    47
                amountTokenToDeposit
    48
            );
    49 0
            _mint(receiver, amountSharesOut);
    50
    51 0
            emit Deposit(
    52 0
                msg.sender,
    53
                receiver,
    54
                tokenIn,
    55
                amountTokenToDeposit,
    56
                amountSharesOut
    57
            );
    58
        }
    59
    60 0
        function exchangeRate() public view virtual returns (uint256) {
    61 0
            uint256 supply = totalSupply;
    62 0
            if (supply == 0) return 1e18;
    63 0
            return (yieldToken.balanceOf(address(this)) * 1e18) / supply;
    64
        }
    65
    66 0
        function getTokensIn() public view virtual returns (address[] memory) {
    67 0
            return tokensIn;
    68
        }
    69
    70 0
        function getTokensOut() public view virtual returns (address[] memory) {
    71 0
            return tokensOut;
    72
        }
    73
    74 22×
        function previewDeposit(
    75
            address tokenIn,
    76
            uint256 amountTokenToDeposit
    77
        ) public view virtual returns (uint256 amountSharesOut) {
    78 24×
            require(tokenIn == address(yieldToken), "Invalid token");
    79
            uint256 supply = totalSupply;
    80 10×
            if (supply == 0) {
    81 12×
                return amountTokenToDeposit;
    82
            }
    83 0
            return
    84 0
                (amountTokenToDeposit * supply) /
    85 0
                yieldToken.balanceOf(address(this));
    86
        }
    87
    88 0
        function previewRedeem(
    89
            address tokenOut,
    90
            uint256 amountSharesToRedeem
    91 0
        ) public view virtual returns (uint256 amountTokenOut) {
    92 0
            require(tokenOut == address(yieldToken), "Invalid token");
    93 0
            uint256 supply = totalSupply;
    94 0
            if (supply == 0) {
    95 0
                return amountSharesToRedeem;
    96
            }
    97
            return
    98 0
                (amountSharesToRedeem * yieldToken.balanceOf(address(this))) /
    99 0
                supply;
    100
        }
    101
    }
    102
    103
    enum RevertType {
    104
        NONE,
    105
        THROW,
    106
        OOG,
    107
        RETURN_BOMB,
    108
        REVERT_BOMB
    109
    }
    110
    111 1061×
    contract MockERC5115Tester is ERC5115 {
    112 0
        RevertType public revertBehaviour;
    113 0
        uint256 public totalLosses;
    114 0
        uint256 public totalGains;
    115 0
        uint256 public lossOnWithdraw;
    116
        uint256 public MAX_BPS = 10_000;
    117
    118 28×
        constructor(address _yieldToken) ERC5115(MockERC20(_yieldToken)) {}
    119
    120 0
        function deposit(
    121
            address receiver,
    122
            address tokenIn,
    123
            uint256 amountTokenToDeposit,
    124
            uint256 minSharesOut,
    125
            bool depositFromInternalBalance
    126 0
        ) public override returns (uint256 amountSharesOut) {
    127 0
            _performRevertBehaviour(revertBehaviour);
    128 0
            return
    129 0
                super.deposit(
    130 0
                    receiver,
    131 0
                    tokenIn,
    132 0
                    amountTokenToDeposit,
    133 0
                    minSharesOut,
    134 0
                    depositFromInternalBalance
    135
                );
    136
        }
    137
    138 0
        function redeem(
    139
            address receiver,
    140
            uint256 amountSharesToRedeem,
    141
            address tokenOut,
    142
            uint256 minTokenOut,
    143
            bool burnFromInternalBalance
    144 0
        ) public virtual returns (uint256 amountTokenOut) {
    145 0
            amountTokenOut = previewRedeem(tokenOut, amountSharesToRedeem);
    146
    147 0
            _burn(msg.sender, amountSharesToRedeem);
    148 0
            uint256 lossyAmountTokenOut = amountTokenOut -
    149 0
                ((amountTokenOut * lossOnWithdraw) / MAX_BPS);
    150 0
            MockERC20(tokenOut).transfer(receiver, lossyAmountTokenOut);
    151
    152 0
            emit Redeem(
    153 0
                msg.sender,
    154
                receiver,
    155
                tokenOut,
    156
                amountSharesToRedeem,
    157
                amountTokenOut
    158
            );
    159
        }
    160
    161
        function _performRevertBehaviour(RevertType action) internal pure {
    162 0
            if (action == RevertType.THROW) {
    163 0
                revert("A normal Revert");
    164
            }
    165
    166 0
            if (action == RevertType.OOG) {
    167 0
                uint256 i;
    168 0
                while (true) {
    169 0
                    ++i;
    170
                }
    171
            }
    172
    173 0
            if (action == RevertType.RETURN_BOMB) {
    174 0
                uint256 _bytes = 2_000_000;
    175
                assembly {
    176 0
                    return(0, _bytes)
    177
                }
    178
            }
    179
    180
            if (action == RevertType.REVERT_BOMB) {
    181 0
                uint256 _bytes = 2_000_000;
    182
                assembly {
    183 0
                    revert(0, _bytes)
    184
                }
    185
            }
    186
    187
            return; // NONE
    188
        }
    189
    190 0
        function setRevertBehavior(RevertType rt) public {
    191 0
            revertBehaviour = rt;
    192
        }
    193
    194 11×
        function simulateLoss(uint256 lossAmount) external {
    195 66×
            MockERC20(yieldToken).transfer(address(0xbeef), lossAmount);
    196 11×
            totalLosses += lossAmount;
    197
        }
    198
    199 12×
        function simulateGain(uint256 gainAmount) external {
    200 65×
            MockERC20(yieldToken).transferFrom(
    201
                msg.sender,
    202
                address(this),
    203
                gainAmount
    204
            );
    205 15×
            totalGains += gainAmount;
    206
        }
    207
    208
        /// @dev Set the loss on withdraw as percentage of the assets being withdrawn
    209 16×
        function setLossOnWithdraw(uint256 _lossOnWithdraw) public {
    210 14×
            _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100%
    211
            lossOnWithdraw = _lossOnWithdraw;
    212
        }
    213
    214 0
        function increaseYield(uint256 increasePercentageFP4) public {
    215 0
            require(increasePercentageFP4 <= 10000, "Invalid percentage");
    216 0
            uint256 amount = (yieldToken.balanceOf(address(this)) *
    217 0
                increasePercentageFP4) / 10000;
    218 0
            MockERC20(yieldToken).transferFrom(msg.sender, address(this), amount);
    219
        }
    220
    221 0
        function decreaseYield(uint256 decreasePercentageFP4) public {
    222 0
            require(decreasePercentageFP4 <= 10000, "Invalid percentage");
    223 0
            uint256 amount = (yieldToken.balanceOf(address(this)) *
    224 0
                decreasePercentageFP4) / 10000;
    225 0
            MockERC20(yieldToken).transfer(address(0xbeef), amount);
    226 0
            totalLosses += amount;
    227
        }
    228
    }
    10% test/recon/mocks/MockERC5115YieldSourceOracle.sol
    Lines covered: 6 / 59 (10%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.30;
    3
    4
    import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
    5
    import {MockERC5115Tester} from "./MockERC5115Tester.sol";
    6
    7
    /// @title MockERC5115YieldSourceOracle
    8
    /// @notice Mock oracle for ERC5115 (Standardized Yield) vaults in testing
    9 278×
    contract MockERC5115YieldSourceOracle is IYieldSourceOracle {
    10 0
        mapping(address => bool) public validAssetMap;
    11
        
    12 11×
        function setValidAsset(address asset, bool isValid) external {
    13 27×
            validAssetMap[asset] = isValid;
    14
        }
    15
        
    16 0
        function decimals(address) external pure returns (uint8) {
    17
            // ERC5115 always uses 18 decimals
    18 0
            return 18;
    19
        }
    20
        
    21 48×
        function getShareOutput(
    22
            address yieldSourceAddress,
    23
            address assetIn,
    24
            uint256 assetsIn
    25
        ) external view returns (uint256) {
    26 132×
            return MockERC5115Tester(yieldSourceAddress).previewDeposit(assetIn, assetsIn);
    27
        }
    28
        
    29 0
        function getAssetOutput(
    30
            address yieldSourceAddress,
    31
            address assetOut,
    32
            uint256 sharesIn
    33 0
        ) public view returns (uint256) {
    34 0
            return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn);
    35
        }
    36
        
    37 0
        function getPricePerShare(address yieldSourceAddress) external view returns (uint256) {
    38 0
            return MockERC5115Tester(yieldSourceAddress).exchangeRate();
    39
        }
    40
        
    41 0
        function getBalanceOfOwner(
    42
            address yieldSourceAddress,
    43
            address ownerOfShares
    44 0
        ) external view returns (uint256) {
    45 0
            return MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares);
    46
        }
    47
        
    48 0
        function getTVLByOwnerOfShares(
    49
            address yieldSourceAddress,
    50
            address ownerOfShares
    51 0
        ) external view returns (uint256) {
    52 0
            uint256 shares = MockERC5115Tester(yieldSourceAddress).balanceOf(ownerOfShares);
    53 0
            uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate();
    54 0
            return (shares * exchangeRate) / 1e18;
    55
        }
    56
        
    57 0
        function getTVL(address yieldSourceAddress) external view returns (uint256) {
    58 0
            uint256 totalShares = MockERC5115Tester(yieldSourceAddress).totalSupply();
    59 0
            uint256 exchangeRate = MockERC5115Tester(yieldSourceAddress).exchangeRate();
    60 0
            return (totalShares * exchangeRate) / 1e18;
    61
        }
    62
        
    63 0
        function getPricePerShareMultiple(
    64
            address[] memory yieldSourceAddresses
    65 0
        ) external view returns (uint256[] memory) {
    66 0
            uint256[] memory prices = new uint256[](yieldSourceAddresses.length);
    67 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    68 0
                prices[i] = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate();
    69
            }
    70 0
            return prices;
    71
        }
    72
        
    73 0
        function getTVLByOwnerOfSharesMultiple(
    74
            address[] memory yieldSourceAddresses,
    75
            address[][] memory ownersOfShares
    76 0
        ) external view returns (uint256[][] memory) {
    77 0
            uint256[][] memory result = new uint256[][](yieldSourceAddresses.length);
    78 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    79 0
                result[i] = new uint256[](ownersOfShares[i].length);
    80 0
                uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate();
    81 0
                for (uint256 j = 0; j < ownersOfShares[i].length; j++) {
    82 0
                    uint256 shares = MockERC5115Tester(yieldSourceAddresses[i]).balanceOf(ownersOfShares[i][j]);
    83 0
                    result[i][j] = (shares * exchangeRate) / 1e18;
    84
                }
    85
            }
    86 0
            return result;
    87
        }
    88
        
    89 0
        function getTVLMultiple(
    90
            address[] memory yieldSourceAddresses
    91 0
        ) external view returns (uint256[] memory) {
    92 0
            uint256[] memory tvls = new uint256[](yieldSourceAddresses.length);
    93 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    94 0
                uint256 totalShares = MockERC5115Tester(yieldSourceAddresses[i]).totalSupply();
    95 0
                uint256 exchangeRate = MockERC5115Tester(yieldSourceAddresses[i]).exchangeRate();
    96 0
                tvls[i] = (totalShares * exchangeRate) / 1e18;
    97
            }
    98
            return tvls;
    99
        }
    100
        
    101 0
        function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
    102 0
            return validAssetMap[asset];
    103
        }
    104
        
    105 0
        function isValidUnderlyingAssets(
    106
            address[] memory,
    107
            address[] memory assets
    108 0
        ) external view returns (bool[] memory) {
    109 0
            bool[] memory validities = new bool[](assets.length);
    110 0
            for (uint256 i = 0; i < assets.length; i++) {
    111 0
                validities[i] = validAssetMap[assets[i]];
    112
            }
    113
            return validities;
    114
        }
    115
        
    116 0
        function getAssetOutputWithFees(
    117
            bytes32,
    118
            address yieldSourceAddress,
    119
            address assetOut,
    120
            address,
    121
            uint256 sharesIn
    122 0
        ) external view returns (uint256) {
    123 0
            return MockERC5115Tester(yieldSourceAddress).previewRedeem(assetOut, sharesIn);
    124
        }
    125
    }
    77% test/recon/mocks/MockERC7540Tester.sol
    Lines covered: 137 / 177 (77%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.0;
    3
    4
    import {MockERC20} from "@recon/MockERC20.sol";
    5
    import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
    6
    7
    abstract contract ERC7575 is MockERC20 {
    8 22×
        MockERC20 public immutable asset;
    9
    10
        event Deposit(
    11
            address indexed sender,
    12
            address indexed owner,
    13
            uint256 assets,
    14
            uint256 shares
    15
        );
    16
        event Withdraw(
    17
            address indexed sender,
    18
            address indexed receiver,
    19
            address indexed owner,
    20
            uint256 assets,
    21
            uint256 shares
    22
        );
    23
    24
        constructor(MockERC20 _asset) MockERC20("MockERC7540Tester", "M7540", 18) {
    25
            asset = _asset;
    26
        }
    27
    28
        function share() external view returns (address shareTokenAddress) {
    29
            return address(this);
    30
        }
    31
    32 29×
        function totalAssets() public view virtual returns (uint256) {
    33 295×
            return asset.balanceOf(address(this));
    34
        }
    35
    36
        function convertToShares(
    37
            uint256 assets
    38
        ) public view virtual returns (uint256) {
    39
            uint256 supply = totalSupply;
    40
            return supply == 0 ? assets : (assets * supply) / totalAssets();
    41
        }
    42
    43 24×
        function convertToAssets(
    44
            uint256 shares
    45
        ) public view virtual returns (uint256) {
    46
            uint256 supply = totalSupply;
    47 121×
            return supply == 0 ? shares : (shares * totalAssets()) / supply;
    48
        }
    49
    50
        function maxDeposit(address) public pure virtual returns (uint256) {
    51
            return type(uint256).max;
    52
        }
    53
    54
        function maxMint(address) public pure virtual returns (uint256) {
    55
            return type(uint256).max;
    56
        }
    57
    58 12×
        function maxWithdraw(address owner) public view virtual returns (uint256) {
    59 16×
            return convertToAssets(balanceOf[owner]);
    60
        }
    61
    62 10×
        function maxRedeem(address owner) public view virtual returns (uint256) {
    63 12×
            return balanceOf[owner];
    64
        }
    65
    66 34×
        function previewDeposit(
    67
            uint256 assets
    68
        ) public view virtual returns (uint256) {
    69 16×
            return convertToShares(assets);
    70
        }
    71
    72
        function previewMint(uint256 shares) public view virtual returns (uint256) {
    73
            uint256 supply = totalSupply;
    74
            return supply == 0 ? shares : (shares * totalAssets()) / supply;
    75
        }
    76
    77 34×
        function previewWithdraw(
    78
            uint256 assets
    79
        ) public view virtual returns (uint256) {
    80
            uint256 supply = totalSupply;
    81 52×
            return supply == 0 ? assets : (assets * supply) / totalAssets();
    82
        }
    83
    84
        function previewRedeem(
    85
            uint256 shares
    86
        ) public view virtual returns (uint256) {
    87 16×
            return convertToAssets(shares);
    88
        }
    89
    90 32×
        function deposit(
    91
            uint256 assets,
    92
            address receiver
    93
        ) public virtual returns (uint256 shares) {
    94 14×
            shares = previewDeposit(assets);
    95 150×
            asset.transferFrom(msg.sender, address(this), assets);
    96 12×
            _mint(receiver, shares);
    97 36×
            emit Deposit(msg.sender, receiver, assets, shares);
    98
        }
    99
    100 22×
        function mint(
    101
            uint256 shares,
    102
            address receiver
    103
        ) public virtual returns (uint256 assets) {
    104 18×
            assets = previewMint(shares);
    105 146×
            asset.transferFrom(msg.sender, address(this), assets);
    106 12×
            _mint(receiver, shares);
    107 22×
            emit Deposit(msg.sender, receiver, assets, shares);
    108
        }
    109
    }
    110
    111 1165×
    contract MockERC7540Tester is ERC7575, IERC165 {
    112
        event DepositRequest(
    113
            address indexed controller,
    114
            address indexed owner,
    115
            uint256 indexed requestId,
    116
            address sender,
    117
            uint256 assets
    118
        );
    119
        event RedeemRequest(
    120
            address indexed controller,
    121
            address indexed owner,
    122
            uint256 indexed requestId,
    123
            address sender,
    124
            uint256 shares
    125
        );
    126
        event OperatorSet(
    127
            address indexed controller,
    128
            address indexed operator,
    129
            bool approved
    130
        );
    131
    132
        struct DepositRequestStruct {
    133
            uint256 assets;
    134
            address controller;
    135
            address owner;
    136
            bool fulfilled;
    137
            bool canceled;
    138
        }
    139
    140
        struct RedeemRequestStruct {
    141
            uint256 shares;
    142
            address controller;
    143
            address owner;
    144
            bool fulfilled;
    145
            bool canceled;
    146
        }
    147
    148
        uint256 private _nextRequestId = 1;
    149
    150 0
        mapping(uint256 => DepositRequestStruct) public depositRequests;
    151 0
        mapping(uint256 => RedeemRequestStruct) public redeemRequests;
    152 0
        mapping(address => mapping(address => bool)) public operators;
    153 0
        mapping(uint256 => bool) public pendingCancelDeposit;
    154 0
        mapping(uint256 => bool) public pendingCancelRedeem;
    155
    156
        uint256 public yieldMultiplier = 10000; // 100% in basis points
    157
        uint256 private constant MAX_BPS = 10000;
    158 0
        uint256 public totalLosses;
    159 0
        uint256 public totalGains;
    160 0
        uint256 public lossOnWithdraw;
    161
    162 28×
        constructor(address _asset) ERC7575(MockERC20(_asset)) {}
    163
    164
        // Operator Management
    165 11×
        function setOperator(
    166
            address operator,
    167
            bool approved
    168
        ) external returns (bool) {
    169 37×
            operators[msg.sender][operator] = approved;
    170
            emit OperatorSet(msg.sender, operator, approved);
    171
            return true;
    172
        }
    173
    174 0
        function isOperator(
    175
            address controller,
    176
            address operator
    177 0
        ) external view returns (bool) {
    178 0
            return operators[controller][operator];
    179
        }
    180
    181
        // Async Deposit Flow
    182 11×
        function requestDeposit(
    183
            uint256 assets,
    184
            address controller,
    185
            address owner
    186
        ) external returns (uint256 requestId) {
    187 13×
            requestId = _nextRequestId++;
    188 116×
            depositRequests[requestId] = DepositRequestStruct({
    189
                assets: assets,
    190
                controller: controller,
    191
                owner: owner,
    192
                fulfilled: false,
    193
                canceled: false
    194
            });
    195
    196 72×
            asset.transferFrom(msg.sender, address(this), assets);
    197 17×
            emit DepositRequest(controller, owner, requestId, msg.sender, assets);
    198
        }
    199
    200 0
        function pendingDepositRequest(
    201
            uint256 requestId,
    202
            address controller
    203 0
        ) external view returns (uint256) {
    204 0
            DepositRequestStruct storage request = depositRequests[requestId];
    205
            if (
    206 10×
                request.controller != controller ||
    207 0
                request.fulfilled ||
    208 0
                request.canceled
    209
            ) {
    210 12×
                return 0;
    211
            }
    212 0
            return request.assets;
    213
        }
    214
    215
        function claimableDepositRequest(
    216
            uint256 requestId,
    217
            address controller
    218
        ) external view returns (uint256) {
    219
            DepositRequestStruct storage request = depositRequests[requestId];
    220
            if (
    221
                request.controller != controller ||
    222
                request.fulfilled ||
    223
                request.canceled
    224
            ) {
    225
                return 0;
    226
            }
    227
            return request.assets;
    228
        }
    229
    230 0
        function pendingCancelDepositRequest(
    231
            uint256 requestId,
    232
            address controller
    233 0
        ) external view returns (bool) {
    234 0
            DepositRequestStruct storage request = depositRequests[requestId];
    235
            return
    236 0
                request.controller == controller && pendingCancelDeposit[requestId];
    237
        }
    238
    239
        // Override deposit to handle async requests
    240 13×
        function deposit(
    241
            uint256 assets,
    242
            address receiver,
    243
            address controller
    244
        ) public returns (uint256 shares) {
    245 11×
            require(
    246 34×
                msg.sender == controller || operators[controller][msg.sender],
    247
                "Not authorized"
    248
            );
    249
    250
            // Find and fulfill a matching deposit request
    251 15×
            for (uint256 i = 1; i < _nextRequestId; i++) {
    252 10×
                DepositRequestStruct storage request = depositRequests[i];
    253
                if (
    254 25×
                    request.controller == controller &&
    255
                    !request.fulfilled &&
    256
                    !request.canceled &&
    257
                    request.assets >= assets
    258
                ) {
    259
                    shares = previewDeposit(assets);
    260
                    request.fulfilled = true;
    261
                    _mint(receiver, shares);
    262
    263
                    // Refund excess assets if any
    264
                    if (request.assets > assets) {
    265 80×
                        asset.transfer(controller, request.assets - assets);
    266
                    }
    267
    268 17×
                    emit Deposit(msg.sender, receiver, assets, shares);
    269
                    return shares;
    270
                }
    271
            }
    272
    273
            // Fallback to synchronous deposit if no matching request
    274
            return super.deposit(assets, receiver);
    275
        }
    276
    277 40×
        function withdraw(
    278
            uint256 assets,
    279
            address receiver,
    280
            address owner
    281
        ) public returns (uint256 shares) {
    282 12×
            shares = previewWithdraw(assets);
    283 14×
            if (msg.sender != owner) {
    284 72×
                allowance[owner][msg.sender] -= shares;
    285
            }
    286 12×
            _burn(owner, shares);
    287 48×
            uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS);
    288
    289 118×
            asset.transfer(receiver, lossyAssets);
    290 54×
            emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares);
    291
        }
    292
    293 44×
        function redeem(
    294
            uint256 shares,
    295
            address receiver,
    296
            address owner
    297
        ) public returns (uint256 assets) {
    298 24×
            assets = previewRedeem(shares);
    299 28×
            if (msg.sender != owner) {
    300 72×
                allowance[owner][msg.sender] -= shares;
    301
            }
    302 22×
            _burn(owner, shares);
    303 48×
            uint256 lossyAssets = assets - ((assets * lossOnWithdraw) / MAX_BPS);
    304
    305 118×
            asset.transfer(receiver, lossyAssets);
    306 32×
            emit Withdraw(msg.sender, receiver, owner, lossyAssets, shares);
    307
        }
    308
    309
        // Async Redeem Flow
    310 17×
        function requestRedeem(
    311
            uint256 shares,
    312
            address controller,
    313
            address owner
    314
        ) external returns (uint256 requestId) {
    315 11×
            requestId = _nextRequestId++;
    316 128×
            redeemRequests[requestId] = RedeemRequestStruct({
    317
                shares: shares,
    318
                controller: controller,
    319
                owner: owner,
    320
                fulfilled: false,
    321
                canceled: false
    322
            });
    323
    324 13×
            emit RedeemRequest(controller, owner, requestId, msg.sender, shares);
    325
        }
    326
    327
        function pendingRedeemRequest(
    328
            uint256 requestId,
    329
            address controller
    330
        ) external view returns (uint256) {
    331
            RedeemRequestStruct storage request = redeemRequests[requestId];
    332
            if (
    333
                request.controller != controller ||
    334
                request.fulfilled ||
    335
                request.canceled
    336
            ) {
    337
                return 0;
    338
            }
    339
            return request.shares;
    340
        }
    341
    342 22×
        function claimableRedeemRequest(
    343
            uint256 requestId,
    344
            address controller
    345
        ) external view returns (uint256) {
    346 18×
            RedeemRequestStruct storage request = redeemRequests[requestId];
    347 0
            if (
    348 28×
                request.controller != controller ||
    349 0
                request.fulfilled ||
    350 0
                request.canceled
    351
            ) {
    352 0
                return 0;
    353
            }
    354
            return request.shares;
    355
        }
    356
    357 0
        function pendingCancelRedeemRequest(
    358
            uint256 requestId,
    359
            address controller
    360 0
        ) external view returns (bool) {
    361 0
            RedeemRequestStruct storage request = redeemRequests[requestId];
    362
            return
    363 0
                request.controller == controller && pendingCancelRedeem[requestId];
    364
        }
    365
    366
        // Cancel Operations
    367 22×
        function cancelDepositRequest(
    368
            uint256 requestId,
    369
            address controller
    370
        ) external {
    371 14×
            require(
    372 42×
                msg.sender == controller || operators[controller][msg.sender],
    373
                "Not authorized"
    374
            );
    375 20×
            DepositRequestStruct storage request = depositRequests[requestId];
    376 20×
            require(
    377 30×
                request.controller == controller && !request.fulfilled,
    378
                "Invalid request"
    379
            );
    380
    381 0
            pendingCancelDeposit[requestId] = true;
    382
        }
    383
    384 34×
        function claimCancelDepositRequest(
    385
            uint256 requestId,
    386
            address receiver,
    387
            address controller
    388
        ) external returns (uint256 assets) {
    389 26×
            DepositRequestStruct storage request = depositRequests[requestId];
    390
    391
            assets = request.assets;
    392 18×
            request.canceled = true;
    393 26×
            pendingCancelDeposit[requestId] = false;
    394
    395 121×
            asset.transfer(receiver, assets);
    396
        }
    397
    398 24×
        function cancelRedeemRequest(
    399
            uint256 requestId,
    400
            address controller
    401
        ) external {
    402 12×
            RedeemRequestStruct storage request = redeemRequests[requestId];
    403
    404 22×
            pendingCancelRedeem[requestId] = true;
    405
        }
    406
    407 89×
        function claimCancelRedeemRequest(
    408
            uint256 requestId,
    409
            address receiver,
    410
            address controller
    411
        ) external returns (uint256 shares) {
    412 24×
            RedeemRequestStruct storage request = redeemRequests[requestId];
    413
    414
            shares = request.shares;
    415 18×
            request.canceled = true;
    416 22×
            pendingCancelRedeem[requestId] = false;
    417
    418 10×
            _mint(receiver, shares);
    419
        }
    420
    421
        // Placeholder functions for Centrifuge compatibility
    422 0
        function poolId() external pure returns (uint64) {
    423 0
            return 1;
    424
        }
    425
    426 0
        function trancheId() external pure returns (bytes16) {
    427
            return bytes16(uint128(1));
    428
        }
    429
    430
        // Yield simulation functions (similar to MockERC4626Tester)
    431
        // Primary way that 7540 vaults receive losses is on rounding in redemptions so we just simulate a loss that reduces total asset balance
    432 0
        function increaseYield(uint256 increasePercentageFP4) external {
    433 0
            uint256 amount = (totalAssets() * increasePercentageFP4) / MAX_BPS;
    434 0
            MockERC20(asset).transferFrom(msg.sender, address(this), amount);
    435
        }
    436
    437 0
        function decreaseYield(uint256 decreasePercentageFP4) external {
    438 0
            uint256 amount = (totalAssets() * decreasePercentageFP4) / MAX_BPS;
    439 0
            MockERC20(asset).transfer(address(0xbeef), amount);
    440
        }
    441
    442 12×
        function simulateGain(uint256 gainAmount) external {
    443 75×
            MockERC20(asset).transferFrom(msg.sender, address(this), gainAmount);
    444 15×
            totalGains += gainAmount;
    445
        }
    446
    447 11×
        function simulateLoss(uint256 lossAmount) external {
    448 66×
            MockERC20(asset).transfer(address(0xbeef), lossAmount);
    449 11×
            totalLosses += lossAmount;
    450
        }
    451
    452
        /// @dev Set the loss on withdraw as percentage of the assets being withdrawn
    453 12×
        function setLossOnWithdraw(uint256 _lossOnWithdraw) public {
    454 11×
            _lossOnWithdraw %= MAX_BPS + 1; // clamp to ensure we set a max of 100%
    455
            lossOnWithdraw = _lossOnWithdraw;
    456
        }
    457
    458
        /// @notice ERC165 interface detection
    459
        function supportsInterface(
    460
            bytes4 interfaceId
    461
        ) external pure override returns (bool) {
    462 0
            return interfaceId == type(IERC165).interfaceId;
    463
        }
    464
    }
    10% test/recon/mocks/MockERC7540YieldSourceOracle.sol
    Lines covered: 6 / 60 (10%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.30;
    3
    4
    import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
    5
    import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    6
    import {IYieldSourceOracle} from "@superform-v2-core/src/interfaces/accounting/IYieldSourceOracle.sol";
    7
    import {MockERC7540Tester} from "./MockERC7540Tester.sol";
    8
    9
    /// @title MockERC7540YieldSourceOracle
    10
    /// @notice Mock oracle for ERC7540 (Asynchronous Tokenized Vaults) in testing
    11 278×
    contract MockERC7540YieldSourceOracle is IYieldSourceOracle {
    12 0
        mapping(address => bool) public validAssetMap;
    13
        
    14 11×
        function setValidAsset(address asset, bool isValid) external {
    15 27×
            validAssetMap[asset] = isValid;
    16
        }
    17
        
    18 0
        function decimals(address yieldSourceAddress) external view returns (uint8) {
    19 0
            address share = MockERC7540Tester(yieldSourceAddress).share();
    20 0
            return IERC20Metadata(share).decimals();
    21
        }
    22
        
    23 48×
        function getShareOutput(
    24
            address yieldSourceAddress,
    25
            address,
    26
            uint256 assetsIn
    27
        ) external view returns (uint256) {
    28 130×
            return MockERC7540Tester(yieldSourceAddress).convertToShares(assetsIn);
    29
        }
    30
        
    31 0
        function getAssetOutput(
    32
            address yieldSourceAddress,
    33
            address,
    34
            uint256 sharesIn
    35 0
        ) public view returns (uint256) {
    36 0
            return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn);
    37
        }
    38
        
    39 0
        function getPricePerShare(address yieldSourceAddress) external view returns (uint256) {
    40 0
            address share = MockERC7540Tester(yieldSourceAddress).share();
    41 0
            uint256 _decimals = IERC20Metadata(share).decimals();
    42 0
            return MockERC7540Tester(yieldSourceAddress).convertToAssets(10 ** _decimals);
    43
        }
    44
        
    45 0
        function getBalanceOfOwner(
    46
            address yieldSourceAddress,
    47
            address ownerOfShares
    48 0
        ) external view returns (uint256) {
    49 0
            return IERC20(MockERC7540Tester(yieldSourceAddress).share()).balanceOf(ownerOfShares);
    50
        }
    51
        
    52 0
        function getTVLByOwnerOfShares(
    53
            address yieldSourceAddress,
    54
            address ownerOfShares
    55 0
        ) external view returns (uint256) {
    56 0
            address share = MockERC7540Tester(yieldSourceAddress).share();
    57 0
            uint256 shares = IERC20(share).balanceOf(ownerOfShares);
    58 0
            return MockERC7540Tester(yieldSourceAddress).convertToAssets(shares);
    59
        }
    60
        
    61 0
        function getTVL(address yieldSourceAddress) external view returns (uint256) {
    62 0
            return MockERC7540Tester(yieldSourceAddress).totalAssets();
    63
        }
    64
        
    65 0
        function getPricePerShareMultiple(
    66
            address[] memory yieldSourceAddresses
    67 0
        ) external view returns (uint256[] memory) {
    68 0
            uint256[] memory prices = new uint256[](yieldSourceAddresses.length);
    69 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    70 0
                address share = MockERC7540Tester(yieldSourceAddresses[i]).share();
    71 0
                uint256 _decimals = IERC20Metadata(share).decimals();
    72 0
                prices[i] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(10 ** _decimals);
    73
            }
    74 0
            return prices;
    75
        }
    76
        
    77 0
        function getTVLByOwnerOfSharesMultiple(
    78
            address[] memory yieldSourceAddresses,
    79
            address[][] memory ownersOfShares
    80 0
        ) external view returns (uint256[][] memory) {
    81 0
            uint256[][] memory result = new uint256[][](yieldSourceAddresses.length);
    82 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    83 0
                result[i] = new uint256[](ownersOfShares[i].length);
    84 0
                address share = MockERC7540Tester(yieldSourceAddresses[i]).share();
    85 0
                for (uint256 j = 0; j < ownersOfShares[i].length; j++) {
    86 0
                    uint256 shares = IERC20(share).balanceOf(ownersOfShares[i][j]);
    87 0
                    result[i][j] = MockERC7540Tester(yieldSourceAddresses[i]).convertToAssets(shares);
    88
                }
    89
            }
    90 0
            return result;
    91
        }
    92
        
    93 0
        function getTVLMultiple(
    94
            address[] memory yieldSourceAddresses
    95 0
        ) external view returns (uint256[] memory) {
    96 0
            uint256[] memory tvls = new uint256[](yieldSourceAddresses.length);
    97 0
            for (uint256 i = 0; i < yieldSourceAddresses.length; i++) {
    98 0
                tvls[i] = MockERC7540Tester(yieldSourceAddresses[i]).totalAssets();
    99
            }
    100
            return tvls;
    101
        }
    102
        
    103 0
        function isValidUnderlyingAsset(address, address asset) external view returns (bool) {
    104 0
            return validAssetMap[asset];
    105
        }
    106
        
    107 0
        function isValidUnderlyingAssets(
    108
            address[] memory,
    109
            address[] memory assets
    110 0
        ) external view returns (bool[] memory) {
    111 0
            bool[] memory validities = new bool[](assets.length);
    112 0
            for (uint256 i = 0; i < assets.length; i++) {
    113 0
                validities[i] = validAssetMap[assets[i]];
    114
            }
    115
            return validities;
    116
        }
    117
        
    118 0
        function getAssetOutputWithFees(
    119
            bytes32,
    120
            address yieldSourceAddress,
    121
            address,
    122
            address,
    123
            uint256 sharesIn
    124 0
        ) external view returns (uint256) {
    125 0
            return MockERC7540Tester(yieldSourceAddress).convertToAssets(sharesIn);
    126
        }
    127
    }
    92% test/recon/targets/AdminTargets.sol
    Lines covered: 187 / 203 (92%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    // External dependencies
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {Panic} from "@recon/Panic.sol";
    8
    import {MockERC20} from "@recon/MockERC20.sol";
    9
    10
    // System dependencies
    11
    import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
    12
    import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
    13
    14
    // Test dependencies
    15
    import {YieldSourceType} from "test/recon/managers/YieldManager.sol";
    16
    import {BeforeAfter, OpType} from "../BeforeAfter.sol";
    17
    import {Properties} from "../Properties.sol";
    18
    19
    abstract contract AdminTargets is BaseTargetFunctions, Properties {
    20
        enum HookType {
    21
            // ERC4626 Hooks
    22
            ApproveAndDeposit4626,
    23
            Deposit4626,
    24
            Redeem4626,
    25
            // ERC5115 Hooks
    26
            ApproveAndDeposit5115,
    27
            Deposit5115,
    28
            Redeem5115,
    29
            // ERC7540 Hooks
    30
            Deposit7540,
    31
            Redeem7540,
    32
            RequestDeposit7540,
    33
            RequestRedeem7540,
    34
            ApproveAndRequestDeposit7540,
    35
            CancelDepositRequest7540,
    36
            CancelRedeemRequest7540,
    37
            ClaimCancelDepositRequest7540,
    38
            ClaimCancelRedeemRequest7540,
    39
            Withdraw7540,
    40
            // Super Vault Hooks
    41
            CancelRedeem,
    42
            SuperVaultWithdraw7540
    43
        }
    44
    45
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    46 15×
        function superVaultStrategy_executeHooks_clamped(
    47
            uint256[] memory hookTypeInts,
    48
            uint256[] memory amountsToInvest,
    49
            bool[] memory usePrevHookAmounts
    50 0
        ) public payable {
    51
            // Limit the number of hooks to 10 maximum
    52
            uint256 numHooks = hookTypeInts.length;
    53
            if (numHooks > 10) {
    54
                numHooks = 10;
    55
            }
    56
    57
            // Ensure all arrays have the same length
    58
            if (amountsToInvest.length < numHooks) {
    59
                numHooks = amountsToInvest.length;
    60
            }
    61
            if (usePrevHookAmounts.length < numHooks) {
    62
                numHooks = usePrevHookAmounts.length;
    63
            }
    64
    65
            // Return early if no hooks to execute
    66
            if (numHooks == 0) {
    67 0
                return;
    68
            }
    69
    70
            // Create ExecuteArgs for the hooks
    71 28×
            ISuperVaultStrategy.ExecuteArgs memory executeArgs = ISuperVaultStrategy
    72
                .ExecuteArgs({
    73 40×
                    hooks: new address[](numHooks),
    74 47×
                    hookCalldata: new bytes[](numHooks),
    75 40×
                    expectedAssetsOrSharesOut: new uint256[](numHooks),
    76 47×
                    globalProofs: new bytes32[][](numHooks),
    77 46×
                    strategyProofs: new bytes32[][](numHooks)
    78
                });
    79
    80
            // Process each hook
    81
            uint256 totalAmountToDeposit;
    82 17×
            for (uint256 i = 0; i < numHooks; i++) {
    83
                // Convert integer to enum (will wrap around if > max enum value)
    84 32×
                HookType hookType = HookType(hookTypeInts[i] % 17); // 17 is the total number of hooks
    85
    86
                // Clamp to the strategy's asset balance (not SuperVault's balance)
    87 24×
                uint256 clampedAmount = amountsToInvest[i] %
    88 128×
                    (MockERC20(superVault.asset()).balanceOf(
    89
                        address(superVaultStrategy)
    90
                    ) + 1);
    91
    92
                // Get the hook address and calldata
    93
                (
    94
                    address hookAddress,
    95
                    bytes memory hookCalldata
    96
                ) = _getHookAddressAndCalldata(
    97
                        hookType,
    98
                        clampedAmount,
    99 15×
                        usePrevHookAmounts[i]
    100
                    );
    101
    102 27×
                executeArgs.hooks[i] = hookAddress;
    103 22×
                executeArgs.hookCalldata[i] = hookCalldata;
    104 23×
                executeArgs.expectedAssetsOrSharesOut[i] = clampedAmount;
    105 49×
                executeArgs.globalProofs[i] = new bytes32[](1);
    106 49×
                executeArgs.strategyProofs[i] = new bytes32[](1);
    107
    108
                totalAmountToDeposit += clampedAmount;
    109
            }
    110
    111
            // Check that amount to be invested is less than the claimable assets so that it doesn't reinvest and prevent users from claiming
    112 12×
            if (_claimableMoreThanInvested(totalAmountToDeposit)) return;
    113
    114
            // Execute all hooks
    115 51×
            this.superVaultStrategy_executeHooks{value: msg.value}(executeArgs);
    116
        }
    117
    118
        function _getHookAddressAndCalldata(
    119
            HookType hookType,
    120
            uint256 amountToInvest,
    121
            bool usePrevHookAmount
    122
        ) internal view returns (address hookAddress, bytes memory hookCalldata) {
    123 14×
            if (hookType == HookType.ApproveAndDeposit4626) {
    124
                hookAddress = address(approveAndDeposit4626Hook);
    125 28×
                hookCalldata = abi.encodePacked(
    126
                    bytes32(0), // yieldSourceOracleId placeholder
    127
                    _getYieldSource(), // Address of the yield source
    128 67×
                    superVault.asset(), // Address of the token to approve and deposit
    129
                    amountToInvest, // Amount to deposit
    130
                    usePrevHookAmount
    131
                );
    132 13×
            } else if (hookType == HookType.Deposit4626) {
    133
                hookAddress = address(deposit4626Hook);
    134 12×
                hookCalldata = abi.encodePacked(
    135
                    bytes32(0), // yieldSourceOracleId placeholder
    136
                    _getYieldSource(), // Address of the yield source
    137
                    amountToInvest, // Amount to deposit
    138
                    usePrevHookAmount
    139
                );
    140 13×
            } else if (hookType == HookType.Redeem4626) {
    141
                hookAddress = address(redeem4626Hook);
    142
                hookCalldata = abi.encodePacked(
    143
                    bytes32(0), // yieldSourceOracleId placeholder
    144
                    _getYieldSource(), // Address of the yield source
    145
                    amountToInvest, // Amount to redeem
    146
                    address(superVaultStrategy), // Receiver
    147
                    address(superVaultStrategy) // Owner
    148
                );
    149 13×
            } else if (hookType == HookType.ApproveAndDeposit5115) {
    150
                hookAddress = address(approveAndDeposit5115Hook);
    151
                hookCalldata = abi.encodePacked(
    152
                    bytes32(0), // yieldSourceOracleId placeholder
    153
                    _getYieldSource(), // Address of the yield source
    154 67×
                    superVault.asset(), // Address of the token to approve and deposit
    155
                    bytes32(0), // tokenId (for ERC5115)
    156
                    amountToInvest, // Amount to deposit
    157
                    usePrevHookAmount
    158
                );
    159 13×
            } else if (hookType == HookType.Deposit5115) {
    160
                hookAddress = address(deposit5115Hook);
    161
                hookCalldata = abi.encodePacked(
    162
                    bytes32(0), // yieldSourceOracleId placeholder
    163
                    _getYieldSource(), // Address of the yield source
    164
                    bytes32(0), // tokenId (for ERC5115)
    165
                    amountToInvest, // Amount to deposit
    166
                    usePrevHookAmount
    167
                );
    168 13×
            } else if (hookType == HookType.Redeem5115) {
    169
                hookAddress = address(redeem5115Hook);
    170
                hookCalldata = abi.encodePacked(
    171
                    bytes32(0), // yieldSourceOracleId placeholder
    172
                    _getYieldSource(), // Address of the yield source
    173
                    bytes32(0), // tokenId (for ERC5115)
    174
                    amountToInvest, // Amount to redeem
    175
                    address(superVaultStrategy), // Receiver
    176
                    address(superVaultStrategy) // Owner
    177
                );
    178 13×
            } else if (hookType == HookType.Deposit7540) {
    179
                hookAddress = address(deposit7540Hook);
    180
                hookCalldata = abi.encodePacked(
    181
                    bytes32(0), // yieldSourceOracleId placeholder
    182
                    _getYieldSource(), // Address of the yield source
    183
                    amountToInvest, // Amount to deposit
    184
                    address(superVaultStrategy), // Receiver
    185
                    address(superVaultStrategy) // Controller
    186
                );
    187 13×
            } else if (hookType == HookType.Redeem7540) {
    188
                hookAddress = address(redeem7540Hook);
    189 10×
                hookCalldata = abi.encodePacked(
    190
                    bytes32(0), // yieldSourceOracleId placeholder
    191
                    _getYieldSource(), // Address of the yield source
    192
                    amountToInvest, // Amount to redeem
    193 10×
                    address(superVaultStrategy), // Receiver
    194
                    address(superVaultStrategy), // Owner
    195
                    address(superVaultStrategy) // Controller
    196
                );
    197 13×
            } else if (hookType == HookType.RequestDeposit7540) {
    198
                hookAddress = address(requestDeposit7540Hook);
    199
                hookCalldata = abi.encodePacked(
    200
                    bytes32(0), // yieldSourceOracleId placeholder
    201
                    _getYieldSource(), // Address of the yield source
    202
                    amountToInvest, // Amount to request deposit
    203
                    address(superVaultStrategy), // Owner
    204
                    address(superVaultStrategy) // Controller
    205
                );
    206 13×
            } else if (hookType == HookType.RequestRedeem7540) {
    207
                hookAddress = address(requestRedeem7540Hook);
    208
                hookCalldata = abi.encodePacked(
    209
                    bytes32(0), // yieldSourceOracleId placeholder
    210
                    _getYieldSource(), // Address of the yield source
    211
                    amountToInvest, // Amount to request redeem
    212
                    address(superVaultStrategy), // Owner
    213
                    address(superVaultStrategy) // Controller
    214
                );
    215 13×
            } else if (hookType == HookType.ApproveAndRequestDeposit7540) {
    216
                hookAddress = address(approveAndRequestDeposit7540Hook);
    217
                hookCalldata = abi.encodePacked(
    218
                    bytes32(0), // yieldSourceOracleId placeholder
    219
                    _getYieldSource(), // Address of the yield source
    220 67×
                    superVault.asset(), // Address of the token to approve
    221
                    amountToInvest, // Amount to request deposit
    222
                    address(superVaultStrategy), // Owner
    223
                    address(superVaultStrategy) // Controller
    224
                );
    225 13×
            } else if (hookType == HookType.CancelDepositRequest7540) {
    226
                hookAddress = address(cancelDepositRequest7540Hook);
    227
                hookCalldata = abi.encodePacked(
    228
                    bytes32(0), // yieldSourceOracleId placeholder
    229
                    _getYieldSource(), // Address of the yield source
    230
                    address(superVaultStrategy) // Controller
    231
                );
    232 13×
            } else if (hookType == HookType.CancelRedeemRequest7540) {
    233
                hookAddress = address(cancelRedeemRequest7540Hook);
    234
                hookCalldata = abi.encodePacked(
    235
                    bytes32(0), // yieldSourceOracleId placeholder
    236
                    _getYieldSource(), // Address of the yield source
    237
                    address(superVaultStrategy) // Controller
    238
                );
    239 13×
            } else if (hookType == HookType.ClaimCancelDepositRequest7540) {
    240
                hookAddress = address(claimCancelDepositRequest7540Hook);
    241
                hookCalldata = abi.encodePacked(
    242
                    bytes32(0), // yieldSourceOracleId placeholder
    243
                    _getYieldSource(), // Address of the yield source
    244
                    address(superVaultStrategy), // Receiver
    245
                    address(superVaultStrategy) // Controller
    246
                );
    247 13×
            } else if (hookType == HookType.ClaimCancelRedeemRequest7540) {
    248
                hookAddress = address(claimCancelRedeemRequest7540Hook);
    249
                hookCalldata = abi.encodePacked(
    250
                    bytes32(0), // yieldSourceOracleId placeholder
    251
                    _getYieldSource(), // Address of the yield source
    252
                    address(superVaultStrategy), // Receiver
    253
                    address(superVaultStrategy) // Controller
    254
                );
    255 13×
            } else if (hookType == HookType.Withdraw7540) {
    256
                hookAddress = address(withdraw7540Hook);
    257
                hookCalldata = abi.encodePacked(
    258
                    bytes32(0), // yieldSourceOracleId placeholder
    259
                    _getYieldSource(), // Address of the yield source
    260
                    amountToInvest, // Amount to withdraw
    261
                    address(superVaultStrategy), // Receiver
    262
                    address(superVaultStrategy), // Owner
    263
                    address(superVaultStrategy) // Controller
    264
                );
    265 12×
            } else if (hookType == HookType.CancelRedeem) {
    266
                hookAddress = address(cancelRedeemHook);
    267
                hookCalldata = abi.encodePacked(
    268
                    bytes32(0), // yieldSourceOracleId placeholder
    269
                    _getYieldSource(), // Address of the yield source
    270
                    address(superVaultStrategy) // Controller
    271
                );
    272
            } else if (hookType == HookType.SuperVaultWithdraw7540) {
    273 0
                hookAddress = address(superVaultWithdraw7540Hook);
    274 0
                hookCalldata = abi.encodePacked(
    275
                    bytes32(0), // yieldSourceOracleId placeholder
    276 0
                    _getYieldSource(), // Address of the yield source
    277 0
                    amountToInvest, // Amount to withdraw
    278 0
                    address(superVaultStrategy), // Receiver
    279
                    address(superVaultStrategy), // Owner
    280
                    address(superVaultStrategy) // Controller
    281
                );
    282
            }
    283
        }
    284
    285 20×
        function superVaultStrategy_fulfillRedeemRequests_clamped(
    286
            uint256 redeemAmount
    287 0
        ) public {
    288
            // Find a controller that has pending redeem requests
    289
            address selectedController = _getActor();
    290 63×
            uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest(
    291
                selectedController
    292
            );
    293
    294
            // Clamp using the actor's pending amount
    295 14×
            uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1);
    296
    297 30×
            address[] memory controllers = new address[](1);
    298 24×
            controllers[0] = selectedController;
    299
    300
            // Determine yield source type from currently active yield source
    301
            YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress(
    302
                _getYieldSource()
    303
            );
    304
            address redeemHook = _getRedeemHookForType(activeYieldSourceType);
    305
    306
            // Create realistic hook calldata for redeem operation
    307
            bytes memory redeemHookCalldata;
    308
    309
            if (
    310 14×
                activeYieldSourceType == YieldSourceType.ERC4626 ||
    311 10×
                activeYieldSourceType == YieldSourceType.ERC5115
    312
            ) {
    313
                // ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount
    314 25×
                redeemHookCalldata = abi.encodePacked(
    315
                    bytes32(0), // yieldSourceOracleId placeholder
    316
                    _getYieldSource(), // Current active yield source
    317
                    address(superVaultStrategy), // Owner (strategy owns the yield source shares)
    318
                    actualRedeemAmount, // Amount to redeem (matches controller's pending request)
    319
                    false // Don't use previous hook amount
    320
                );
    321
            } else {
    322
                // ERC7540 Layout: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount
    323 27×
                redeemHookCalldata = abi.encodePacked(
    324
                    bytes32(0), // yieldSourceOracleId placeholder
    325
                    _getYieldSource(), // Current active yield source
    326
                    actualRedeemAmount, // Amount to redeem (matches controller's pending request)
    327
                    false // Don't use previous hook amount
    328
                );
    329
            }
    330
    331
            // Create arrays for FulfillArgs
    332 29×
            address[] memory hooks = new address[](1);
    333 26×
            hooks[0] = redeemHook;
    334
    335 37×
            bytes[] memory hookCalldata = new bytes[](1);
    336 20×
            hookCalldata[0] = redeemHookCalldata;
    337
    338 30×
            uint256[] memory expectedAssetsOrSharesOut = new uint256[](1);
    339 20×
            expectedAssetsOrSharesOut[0] = 1; // @audit Allow max losse amount matching the actual redeem
    340
    341 34×
            bytes32[][] memory globalProofs = new bytes32[][](1);
    342 33×
            globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator
    343
    344 34×
            bytes32[][] memory strategyProofs = new bytes32[][](1);
    345 32×
            strategyProofs[0] = new bytes32[](0); // Empty proof
    346
    347
            // Create the FulfillArgs struct
    348 34×
            ISuperVaultStrategy.FulfillArgs memory fulfillArgs = ISuperVaultStrategy
    349
                .FulfillArgs({
    350
                    controllers: controllers,
    351
                    hooks: hooks,
    352
                    hookCalldata: hookCalldata,
    353
                    expectedAssetsOrSharesOut: expectedAssetsOrSharesOut,
    354
                    globalProofs: globalProofs,
    355
                    strategyProofs: strategyProofs
    356
                });
    357
    358
            // Execute the function
    359
            superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT(fulfillArgs);
    360
        }
    361
    362
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    363
    364 22×
        function superVaultStrategy_executeHooks(
    365
            ISuperVaultStrategy.ExecuteArgs memory args
    366
        ) public payable asAdmin {
    367 110×
            superVaultStrategy.executeHooks{value: msg.value}(args);
    368
    369 0
            executeHooksSuccess = true;
    370
        }
    371
    372
        /// @dev Property: superVaultStrategy does not incur loss on fulfillment
    373 20×
        function superVaultStrategy_fulfillRedeemRequests_ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT(
    374
            ISuperVaultStrategy.FulfillArgs memory args
    375
        ) public updateGhostsWithOpType(OpType.FULFILL) {
    376
            uint256 summedExpectedAssets;
    377 17×
            for (uint256 i; i < args.expectedAssetsOrSharesOut.length; i++) {
    378 26×
                summedExpectedAssets += args.expectedAssetsOrSharesOut[i];
    379
            }
    380
    381
            // no need to prank because called as admin address(this)
    382 53×
            superVaultStrategy.fulfillRedeemRequests(args);
    383
    384 0
            uint256 assetBalanceAfter = MockERC20(superVault.asset()).balanceOf(
    385 0
                address(superVaultStrategy)
    386
            );
    387
    388 0
            gte(
    389 0
                assetBalanceAfter,
    390 0
                summedExpectedAssets,
    391 0
                ASSERTION_STRATEGY_NO_LOSS_ON_FULFILLMENT
    392
            );
    393
        }
    394
    395
        // Functions that require SuperGovernor access
    396
        /// @dev removed because we're bypassing hook validation
    397
        // function superVaultAggregator_setHooksRootUpdateTimelock(
    398
        //     uint256 newTimelock
    399
        // ) public asAdmin {
    400
        //     superVaultAggregator.setHooksRootUpdateTimelock(newTimelock);
    401
        // }
    402
    403
        /// @dev removed because we're bypassing hook validation
    404
        // function superVaultAggregator_proposeGlobalHooksRoot(
    405
        //     bytes32 newRoot
    406
        // ) public asAdmin {
    407
        //     superVaultAggregator.proposeGlobalHooksRoot(newRoot);
    408
        // }
    409
    410
        /// @dev removed because we're bypassing hook validation
    411
        // function superVaultAggregator_executeGlobalHooksRootUpdate()
    412
        //     public
    413
        //     asAdmin
    414
        // {
    415
        //     superVaultAggregator.executeGlobalHooksRootUpdate();
    416
        // }
    417
    418
        /// @dev removed because we're bypassing hook validation
    419
        // function superVaultAggregator_setGlobalHooksRootVetoStatus(
    420
        //     bool vetoed
    421
        // ) public asAdmin {
    422
        //     superVaultAggregator.setGlobalHooksRootVetoStatus(vetoed);
    423
        // }
    424
    425
        /// @dev removed because we're bypassing hook validation
    426
        // function superVaultAggregator_setStrategyHooksRootVetoStatus(
    427
        //     address strategy,
    428
        //     bool vetoed
    429
        // ) public asAdmin {
    430
        //     superVaultAggregator.setStrategyHooksRootVetoStatus(strategy, vetoed);
    431
        // }
    432
    433 20×
        function superVaultAggregator_changePrimaryManager(
    434
            address strategy,
    435
            address newManager
    436
        ) public asAdmin {
    437 22×
            superVaultAggregator.changePrimaryManager(strategy, newManager);
    438
        }
    439
    440
        /// @dev won't achieve coverage until issue outlined here is resolved: https://github.com/Recon-Fuzz/superform-review/issues/5
    441 20×
        function superVaultAggregator_slashStake(
    442
            address manager,
    443
            uint256 amount
    444
        ) public asAdmin {
    445 22×
            superVaultAggregator.slashStake(manager, amount);
    446
        }
    447
    448
        /// Helpers
    449
    450
        function _requestedSharesForControllers(
    451
            address[] memory controllers
    452
        ) internal returns (uint256) {
    453
            uint256 totalRequested;
    454
            for (uint256 i; i < controllers.length; i++) {
    455
                totalRequested += superVault.pendingRedeemRequest(
    456
                    0,
    457
                    controllers[i]
    458
                );
    459
            }
    460
    461
            return totalRequested;
    462
        }
    463
    464
        function _sumSuperVaultValsForControllers(
    465
            address[] memory controllers
    466
        )
    467
            internal
    468
            view
    469
            returns (uint256 sumAccumulatorShares, uint256 sumAccumulatorCostBasis)
    470
        {
    471
            for (uint256 i; i < controllers.length; i++) {
    472
                sumAccumulatorShares += superVaultStrategy
    473
                    .getSuperVaultState(controllers[i])
    474
                    .accumulatorShares;
    475
                sumAccumulatorCostBasis += superVaultStrategy
    476
                    .getSuperVaultState(controllers[i])
    477
                    .accumulatorCostBasis;
    478
            }
    479
        }
    480
    481
        function _claimableMoreThanInvested(
    482
            uint256 totalAmountToDeposit
    483
        ) internal returns (bool) {
    484
            address[] memory actors = _getActors();
    485
            uint256 totalClaimable;
    486 14×
            for (uint256 i; i < actors.length; i++) {
    487 74×
                uint256 claimableRedemptions = superVault.claimableRedeemRequest(
    488
                    0,
    489 17×
                    actors[i]
    490
                );
    491 63×
                uint256 claimableRedemptionsAsAssets = superVault.convertToAssets(
    492
                    claimableRedemptions
    493
                );
    494
                totalClaimable += claimableRedemptionsAsAssets;
    495
            }
    496
    497 124×
            uint256 currentStrategyBalance = MockERC20(superVault.asset())
    498
                .balanceOf(address(superVaultStrategy));
    499
    500
            // Don't allow investing more than the claimable amount
    501
            if (totalAmountToDeposit > totalClaimable) {
    502
                return true;
    503
            }
    504
    505
            // Ensure strategy has sufficient assets remaining after investment to cover claimable amounts
    506
            uint256 remainingStrategyBalance = currentStrategyBalance -
    507
                totalAmountToDeposit;
    508
            if (remainingStrategyBalance < totalClaimable) {
    509 0
                return true;
    510
            }
    511
    512
            return false;
    513
        }
    514
    }
    88% test/recon/targets/DoomsdayTargets.sol
    Lines covered: 331 / 375 (88%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    import {vm} from "@chimera/Hevm.sol";
    6
    import {Panic} from "@recon/Panic.sol";
    7
    import {MockERC20} from "@recon/MockERC20.sol";
    8
    import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    9
    10
    import {BeforeAfter} from "../BeforeAfter.sol";
    11
    import {Properties} from "../Properties.sol";
    12
    import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
    13
    import {YieldSourceType} from "test/recon/managers/YieldManager.sol";
    14
    import {MockERC4626Tester} from "test/recon/mocks/MockERC4626Tester.sol";
    15
    import {MockERC5115Tester} from "test/recon/mocks/MockERC5115Tester.sol";
    16
    import {MockERC7540Tester} from "test/recon/mocks/MockERC7540Tester.sol";
    17
    18
    abstract contract DoomsdayTargets is BaseTargetFunctions, Properties {
    19
        /// @dev Property: previewDeposit and deposit equivalence
    20 20×
        function doomsday_previewDepositEquivalence_ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE(
    21
            uint256 assets
    22
        ) public stateless {
    23 69×
            uint256 previewDepositShares = superVault.previewDeposit(assets);
    24
    25 41×
            vm.prank(_getActor());
    26 82×
            uint256 sharesActualDeposit = superVault.deposit(assets, _getActor());
    27
    28
            eq(
    29
                previewDepositShares,
    30
                sharesActualDeposit,
    31 17×
                ASSERTION_PREVIEW_DEPOSIT_EQUIVALENCE
    32
            );
    33
        }
    34
    35
        /// @dev Property: previewMint and mint equivalence
    36 22×
        function doomsday_previewMintEquivalence_ASSERTION_PREVIEW_MINT_EQUIVALENCE(uint256 shares) public stateless {
    37 69×
            uint256 previewMintAssets = superVault.previewMint(shares);
    38
    39 41×
            vm.prank(_getActor());
    40 82×
            uint256 assetsActualMint = superVault.mint(shares, _getActor());
    41
    42
            eq(
    43
                previewMintAssets,
    44
                assetsActualMint,
    45 17×
                ASSERTION_PREVIEW_MINT_EQUIVALENCE
    46
            );
    47
        }
    48
    49
        /// @dev Property: mint/redeem doesn't cause loss to user
    50 20×
        function doomsday_mintRedeemSymmetrical_ASSERTION_MINT_REDEEM_SYMMETRICAL(
    51
            uint256 sharesToMint
    52 14×
        ) public stateless {
    53
            // skip if there's been any gain because it complicates the assertion checking
    54
            // NOTE: removed because was previously checking that user doesn't gain only from minting/redeeming
    55
            // if (MockERC4626Tester(_getYieldSource()).totalGains() > 0) {
    56
            //     return;
    57
            // }
    58 70×
            address asset = superVault.asset();
    59 64×
            uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor());
    60 60×
            uint256 feeRecipientBalanceBefore = MockERC20(asset).balanceOf(
    61
                feeRecipient
    62
            );
    63
    64
            // 1. Mint
    65 41×
            vm.prank(_getActor());
    66 82×
            uint256 assetsUsed = superVault.mint(sharesToMint, _getActor());
    67
    68
            // 2. Deposit assets into yield strategy via executeHooks
    69
            // This is needed because the user's assets are currently in the strategy contract
    70
            // but not yet deposited into the underlying yield source
    71 60×
            uint256 strategyAssetBalance = MockERC20(asset).balanceOf(
    72
                address(superVaultStrategy)
    73
            );
    74
            if (strategyAssetBalance > 0) {
    75
                ISuperVaultStrategy.ExecuteArgs
    76
                    memory depositArgs = _createExecuteDepositArgs(
    77
                        strategyAssetBalance
    78
                    );
    79
    80 54×
                superVaultStrategy.executeHooks(depositArgs);
    81
            }
    82
    83
            // 3. Request Redemption
    84 66×
            uint256 userShares = superVault.balanceOf(_getActor());
    85
            // get shares of strategy in the deposited yield source
    86
            uint256 shares = _getSuperVaultStrategyShares();
    87
    88 41×
            vm.prank(_getActor());
    89 80×
            superVault.requestRedeem(shares, _getActor(), _getActor());
    90
    91 124×
            uint256 feeBalanceBefore = MockERC20(superVault.asset()).balanceOf(
    92
                feeRecipient
    93
            );
    94
    95
            // 4. Fulfill Redemption from yield strategy
    96
            // Now we need to redeem from the yield strategy to get assets back
    97
            ISuperVaultStrategy.FulfillArgs
    98
                memory fulfillArgs = _createFulfillRedeemFromStrategyArgs(shares);
    99
    100
            // called by admin address(this)
    101 50×
            superVaultStrategy.fulfillRedeemRequests(fulfillArgs);
    102
    103
            // 5. Claim Redemption
    104 66×
            uint256 sharesToRedeem = superVault.maxRedeem(_getActor());
    105
    106 41×
            vm.prank(_getActor());
    107 74×
            superVault.redeem(sharesToRedeem, _getActor(), _getActor());
    108
    109 64×
            uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor());
    110
    111
            uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost
    112
    113 122×
            uint256 feeRecipientBalanceAfter = MockERC20(superVault.asset())
    114
                .balanceOf(feeRecipient);
    115
            uint256 feeDelta = feeRecipientBalanceAfter - feeRecipientBalanceBefore;
    116
    117
            // 6. Check that user didn't lose assets
    118
            gte(
    119 12×
                balanceAfter + TOLERANCE + feeDelta,
    120
                balanceBefore,
    121 17×
                ASSERTION_MINT_REDEEM_SYMMETRICAL
    122
            );
    123
        }
    124
    125
        /// @dev Property: deposit/withdraw doesn't cause loss to user
    126 20×
        function doomsday_depositWithdrawSymmetrical_ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL(
    127
            uint256 assetsToDeposit
    128
        ) public stateless returns (uint256, uint256) {
    129
            // skip if there's been any gain because it complicates the assertion checking
    130
            // NOTE: removed because was previously checking that user doesn't gain only from minting/redeeming
    131
            // if (MockERC4626Tester(_getYieldSource()).totalGains() > 0) {
    132
            //     return;
    133
            // }
    134 70×
            address asset = superVault.asset();
    135 63×
            uint256 balanceBefore = MockERC20(asset).balanceOf(_getActor());
    136
    137
            // 1. Deposit
    138 41×
            vm.prank(_getActor());
    139 79×
            superVault.deposit(assetsToDeposit, _getActor());
    140
    141
            // 2. Request Withdrawal (through redemption in ERC7540)
    142 65×
            uint256 shares = superVault.balanceOf(_getActor());
    143 41×
            vm.prank(_getActor());
    144 74×
            superVault.requestRedeem(shares, _getActor(), _getActor());
    145
    146
            // 3. Fulfill Withdrawal
    147
            ISuperVaultStrategy.FulfillArgs
    148
                memory fulfillArgs = _createFulfillRedeemArgs(shares);
    149
            // fulfills as admin (address(this))
    150 50×
            superVaultStrategy.fulfillRedeemRequests(fulfillArgs);
    151
    152
            // 4. Claim Withdrawal
    153 66×
            uint256 withdrawableAssets = superVault.maxWithdraw(_getActor());
    154 41×
            vm.prank(_getActor());
    155 74×
            superVault.withdraw(withdrawableAssets, _getActor(), _getActor());
    156
    157 64×
            uint256 balanceAfter = MockERC20(asset).balanceOf(_getActor());
    158
    159
            uint256 TOLERANCE = 10; // 10 wei max tolerance of assets lost
    160
            // 5. Check that user didn't lose assets
    161
            gte(
    162
                balanceAfter + TOLERANCE,
    163
                balanceBefore,
    164 17×
                ASSERTION_DEPOSIT_WITHDRAW_SYMMETRICAL
    165
            );
    166
    167
            return (balanceAfter, balanceBefore);
    168
        }
    169
    170
        /// @dev Property: maxRedeem is reset to 0 after full redemption
    171
        /// @dev Property: redeeming maxRedeem shouldn't revert
    172 20×
        function doomsday_maxRedeemResetsAfterFullRedemption_ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION(
    173
            uint256 sharesToMint
    174
        ) public stateless {
    175
            // 1. Deposit to get shares
    176 41×
            vm.prank(_getActor());
    177 79×
            superVault.mint(sharesToMint, _getActor());
    178
    179
            // redeem all user shares
    180 65×
            uint256 shares = superVault.balanceOf(_getActor());
    181
    182
            // 2. Request full redemption
    183 41×
            vm.prank(_getActor());
    184 74×
            superVault.requestRedeem(shares, _getActor(), _getActor());
    185
    186
            // 3. Fulfill the redemption request
    187
            ISuperVaultStrategy.FulfillArgs
    188
                memory fulfillArgs = _createFulfillRedeemArgs(shares);
    189
            // fulfill as address(this)
    190 50×
            superVaultStrategy.fulfillRedeemRequests(fulfillArgs);
    191
    192
            // 4. Check maxRedeem before claiming
    193 66×
            uint256 maxRedeemBeforeClaim = superVault.maxRedeem(_getActor());
    194
    195
            // 5. Claim the full redemption
    196 41×
            vm.prank(_getActor());
    197 74×
            try superVault.redeem(maxRedeemBeforeClaim, _getActor(), _getActor()) {
    198
                // 6. Check maxRedeem is reset to 0 after full redemption
    199 66×
                uint256 maxRedeemAfterClaim = superVault.maxRedeem(_getActor());
    200
                eq(
    201
                    maxRedeemAfterClaim,
    202
                    0,
    203 17×
                    ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION
    204
                );
    205
            } catch {
    206 0
                if (maxRedeemBeforeClaim > 0) {
    207 0
                    t(false, ASSERTION_MAX_REDEEM_RESETS_AFTER_FULL_REDEMPTION);
    208
                }
    209
            }
    210
        }
    211
    212
        /// @dev Property: maxWithdraw is reset to 0 after full withdrawal
    213 20×
        function doomsday_maxWithdrawResetsAfterFullWithdrawal_ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL(
    214
            uint256 assetsToDeposit
    215
        ) public stateless {
    216
            // 1. Deposit to get shares
    217 41×
            vm.prank(_getActor());
    218 79×
            superVault.deposit(assetsToDeposit, _getActor());
    219
    220 65×
            uint256 shares = superVault.balanceOf(_getActor());
    221
    222
            // 2. Request redemption of all shares
    223 41×
            vm.prank(_getActor());
    224 74×
            superVault.requestRedeem(shares, _getActor(), _getActor());
    225
    226
            // 3. Fulfill the redemption request
    227
            ISuperVaultStrategy.FulfillArgs
    228
                memory fulfillArgs = _createFulfillRedeemArgs(shares);
    229
            // called as admin address(this)
    230 50×
            superVaultStrategy.fulfillRedeemRequests(fulfillArgs);
    231
    232
            // 4. Check maxWithdraw after fulfillment and use that value
    233 66×
            uint256 maxWithdrawBefore = superVault.maxWithdraw(_getActor());
    234
    235
            // 5. Withdraw the exact amount returned by maxWithdraw
    236 41×
            vm.prank(_getActor());
    237 74×
            try superVault.withdraw(maxWithdrawBefore, _getActor(), _getActor()) {
    238
                // 6. Check maxWithdraw is reset to 0 after full withdrawal
    239 66×
                uint256 maxWithdrawAfter = superVault.maxWithdraw(_getActor());
    240
                eq(
    241
                    maxWithdrawAfter,
    242
                    0,
    243 17×
                    ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL
    244
                );
    245
            } catch {
    246 0
                t(false, ASSERTION_MAX_WITHDRAW_RESETS_AFTER_FULL_WITHDRAWAL);
    247
            }
    248
        }
    249
    250
        /// @dev Property: fulfillRedeemRequests doesn't redeem more than requested for multiple actors
    251 20×
        function doomsday_fulfillDoesntOverRedeemMultipleActors_ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS(
    252
            uint256[3] memory sharesToMint,
    253
            uint256[3] memory actorIndexes
    254 0
        ) public stateless {
    255
            address[] memory actors = _getActors();
    256
            if (actors.length < 3) return; // Need at least 3 actors for this test
    257
    258
            // Arrays to track actors and their requests
    259 25×
            address[] memory testActors = new address[](3);
    260 26×
            uint256[] memory requestedShares = new uint256[](3);
    261 27×
            uint256[] memory sharesBefore = new uint256[](3);
    262
    263
            // 1. Setup: Each actor deposits and requests redemption
    264
            uint256 totalRequestedShares;
    265 13×
            for (uint256 i = 0; i < 3; i++) {
    266
                // Get unique actor
    267 57×
                testActors[i] = actors[actorIndexes[i] % actors.length];
    268
    269
                // Mint shares for this actor
    270 18×
                if (sharesToMint[i] > 0) {
    271 58×
                    vm.prank(testActors[i]);
    272 104×
                    superVault.mint(sharesToMint[i], testActors[i]);
    273
                }
    274
    275
                // Get actual share balance
    276 103×
                sharesBefore[i] = superVault.maxRedeem(testActors[i]);
    277 34×
                requestedShares[i] = sharesBefore[i];
    278
    279
                // Request redemption of all shares
    280 21×
                if (requestedShares[i] > 0) {
    281 0
                    vm.prank(testActors[i]);
    282 0
                    superVault.requestRedeem(
    283 0
                        requestedShares[i],
    284 0
                        testActors[i],
    285 0
                        testActors[i]
    286
                    );
    287
                }
    288 23×
                totalRequestedShares += requestedShares[i];
    289
            }
    290
    291
            // 2. Create multi-actor FulfillArgs
    292
            ISuperVaultStrategy.FulfillArgs
    293
                memory fulfillArgs = _createMultiActorFulfillArgs(
    294
                    testActors,
    295
                    requestedShares
    296
                );
    297
    298
            // 3. Calculate total pending before
    299
            uint256 totalPendingBefore;
    300 12×
            for (uint256 i = 0; i < 3; i++) {
    301 79×
                totalPendingBefore += superVault.pendingRedeemRequest(
    302
                    0,
    303 17×
                    testActors[i]
    304
                );
    305
            }
    306
    307
            // 4. Fulfill all redemption requests at once
    308
            // fulfill as address(this)
    309 53×
            superVaultStrategy.fulfillRedeemRequests(fulfillArgs);
    310
    311
            // 5. Calculate total pending after
    312 0
            uint256 totalPendingAfter;
    313 0
            for (uint256 i = 0; i < 3; i++) {
    314 0
                totalPendingAfter += superVault.pendingRedeemRequest(
    315
                    0,
    316 0
                    testActors[i]
    317
                );
    318
            }
    319
    320 0
            lte(
    321 0
                totalPendingBefore - totalPendingAfter,
    322 0
                totalRequestedShares,
    323 0
                ASSERTION_FULFILL_DOESNT_OVER_REDEEM_MULTIPLE_ACTORS
    324
            );
    325
        }
    326
    327
        /// @dev Property: primary manager can always be replaced by governance via `changePrimaryManager`
    328 15×
        function doomsday_primaryManagerAlwaysChangeable_ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE() public stateless {
    329
            address strategy = address(superVaultStrategy);
    330
            address newManager = _getActor();
    331
    332
            // Since address(this) has SUPER_GOVERNOR_ROLE, this should always succeed
    333 36×
            vm.prank(address(this));
    334 56×
            try superGovernor.changePrimaryManager(strategy, newManager) {
    335
                // Call succeeded - this is expected behavior
    336 0
            } catch (bytes memory err) {
    337 0
                bool expectedError;
    338 0
                expectedError = checkError(err, "MANAGER_TAKEOVERS_FROZEN()"); // custom error
    339 0
                t(
    340 0
                    !expectedError,
    341 0
                    ASSERTION_PRIMARY_MANAGER_ALWAYS_CHANGEABLE
    342
                );
    343
            }
    344
        }
    345
    346
        /// @dev Property: all users can withdraw (solvency)
    347
        // NOTE: if withdrawing from a given strategy via fulfillRedeemRequests fails, it can be expected that one of the YieldSourceTargets would be called to switch the yield source
    348
        // this should allow fulfillments to eventually succeed so we don't need to sort through all yield sources that have currently been deposited into before fulfilling
    349 15×
        function doomsday_allUsersCanWithdraw_ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED() public stateless {
    350
            address[] memory actors = _getActors();
    351 62×
            bool paused = superVaultAggregator.isStrategyPaused(
    352
                address(superVaultStrategy)
    353
            );
    354
    355
            // request redemption for all actors
    356 15×
            for (uint256 i; i < actors.length; i++) {
    357 86×
                uint256 redeemableShares = superVault.balanceOf(actors[i]);
    358
    359 58×
                vm.prank(actors[i]);
    360 113×
                superVault.requestRedeem(redeemableShares, actors[i], actors[i]);
    361
            }
    362
    363
            // fulfill redemption for all actors
    364
            for (uint256 i; i < actors.length; i++) {
    365 74×
                uint256 redeemableShares = superVault.pendingRedeemRequest(
    366
                    0,
    367 17×
                    actors[i]
    368
                );
    369
    370
                // switch the actor
    371
                _switchActor(i);
    372
    373
                ISuperVaultStrategy.FulfillArgs
    374
                    memory fulfillArgs = _fulfillRedeemRequestsArgs(
    375
                        redeemableShares
    376
                    );
    377 49×
                superVaultStrategy.fulfillRedeemRequests(fulfillArgs);
    378
            }
    379
    380
            // try to withdraw max possible for all actors
    381 0
            for (uint256 i; i < actors.length; i++) {
    382 0
                uint256 withdrawable = superVault.maxWithdraw(actors[i]);
    383
    384 0
                if (withdrawable > 0 && !paused) {
    385 0
                    vm.prank(actors[i]);
    386 0
                    try
    387 0
                        superVault.withdraw(withdrawable, actors[i], actors[i])
    388
                    {} catch {
    389
                        // if user can't maxWithdraw there's most likely an insolvency issue related to the TOLERANCE_CONSTANT
    390 0
                        t(
    391 0
                            false,
    392 0
                            ASSERTION_ALL_USERS_CAN_WITHDRAW_WHEN_UNPAUSED
    393
                        );
    394
                    }
    395
                }
    396
            }
    397
        }
    398
    399
        /// @dev Property: Claiming redemptions should never revert with INVALID_REDEEM_CLAIM
    400 20×
        function doomsday_redemptionsNeverReverts_ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM(
    401
            uint256 shares
    402
        ) public asActor stateless {
    403 85×
            try superVault.redeem(shares, _getActor(), _getActor()) {} catch (
    404
                bytes memory err
    405
            ) {
    406 24×
                bool unexpectedError = checkError(err, "INVALID_REDEEM_CLAIM()");
    407
                t(
    408
                    !unexpectedError,
    409 17×
                    ASSERTION_REDEEM_SHOULD_NOT_REVERT_INVALID_REDEEM_CLAIM
    410
                );
    411
            }
    412
        }
    413
    414
        /// @dev Get shares for SuperVaultStrategy in the current yield source
    415
        function _getSuperVaultStrategyShares() internal view returns (uint256) {
    416
            address yieldSource = _getYieldSource();
    417
            YieldSourceType sourceType = _getYieldSourceTypeFromAddress(
    418
                yieldSource
    419
            );
    420
    421 12×
            if (sourceType == YieldSourceType.ERC4626) {
    422
                return
    423 57×
                    MockERC4626Tester(yieldSource).balanceOf(
    424
                        address(superVaultStrategy)
    425
                    );
    426 0
            } else if (sourceType == YieldSourceType.ERC5115) {
    427
                return
    428 0
                    MockERC5115Tester(yieldSource).balanceOf(
    429 0
                        address(superVaultStrategy)
    430
                    );
    431 0
            } else if (sourceType == YieldSourceType.ERC7540) {
    432
                return
    433 0
                    MockERC7540Tester(yieldSource).balanceOf(
    434 0
                        address(superVaultStrategy)
    435
                    );
    436
            } else {
    437 0
                revert("Invalid yield source type");
    438
            }
    439
        }
    440
    441
        // Helpers
    442
    443
        /// @dev Helper function to clamp the values for the function call
    444 33×
        function _fulfillRedeemRequestsArgs(
    445
            uint256 redeemAmount
    446
        ) public returns (ISuperVaultStrategy.FulfillArgs memory fulfillArgs) {
    447
            // Find a controller that has pending redeem requests
    448
            address selectedController = _getActor();
    449 126×
            uint256 pendingAmount = superVaultStrategy.pendingRedeemRequest(
    450
                selectedController
    451
            );
    452
    453
            // Clamp using the actor's pending amount
    454 28×
            uint256 actualRedeemAmount = redeemAmount % (pendingAmount + 1);
    455
    456 60×
            address[] memory controllers = new address[](1);
    457 48×
            controllers[0] = selectedController;
    458
    459
            // Determine yield source type from currently active yield source
    460 10×
            YieldSourceType activeYieldSourceType = _getYieldSourceTypeFromAddress(
    461
                _getYieldSource()
    462
            );
    463 14×
            address redeemHook = _getRedeemHookForType(activeYieldSourceType);
    464
    465
            // Create realistic hook calldata for redeem operation
    466
            bytes memory redeemHookCalldata;
    467
    468 14×
            if (
    469 28×
                activeYieldSourceType == YieldSourceType.ERC4626 ||
    470 20×
                activeYieldSourceType == YieldSourceType.ERC5115
    471
            ) {
    472
                // ERC4626/ERC5115 Layout: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount
    473 50×
                redeemHookCalldata = abi.encodePacked(
    474
                    bytes32(0), // yieldSourceOracleId placeholder
    475
                    _getYieldSource(), // Current active yield source
    476 12×
                    address(superVaultStrategy), // Owner (strategy owns the yield source shares)
    477
                    actualRedeemAmount, // Amount to redeem (matches controller's pending request)
    478
                    false // Don't use previous hook amount
    479
                );
    480
            } else {
    481
                // ERC7540 Layout: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount
    482 54×
                redeemHookCalldata = abi.encodePacked(
    483
                    bytes32(0), // yieldSourceOracleId placeholder
    484
                    _getYieldSource(), // Current active yield source
    485
                    actualRedeemAmount, // Amount to redeem (matches controller's pending request)
    486
                    false // Don't use previous hook amount
    487
                );
    488
            }
    489
    490
            // Create arrays for FulfillArgs
    491 58×
            address[] memory hooks = new address[](1);
    492 52×
            hooks[0] = redeemHook;
    493
    494 74×
            bytes[] memory hookCalldata = new bytes[](1);
    495 40×
            hookCalldata[0] = redeemHookCalldata;
    496
    497 60×
            uint256[] memory expectedAssetsOrSharesOut = new uint256[](1);
    498 40×
            expectedAssetsOrSharesOut[0] = actualRedeemAmount; // Expect amount matching the actual redeem
    499
    500 68×
            bytes32[][] memory globalProofs = new bytes32[][](1);
    501 66×
            globalProofs[0] = new bytes32[](0); // Empty proof for UnsafeSuperVaultAggregator
    502
    503 68×
            bytes32[][] memory strategyProofs = new bytes32[][](1);
    504 74×
            strategyProofs[0] = new bytes32[](0); // Empty proof
    505
    506
            // Create the FulfillArgs struct
    507 78×
            fulfillArgs = ISuperVaultStrategy.FulfillArgs({
    508
                controllers: controllers,
    509
                hooks: hooks,
    510
                hookCalldata: hookCalldata,
    511
                expectedAssetsOrSharesOut: expectedAssetsOrSharesOut,
    512
                globalProofs: globalProofs,
    513
                strategyProofs: strategyProofs
    514
            });
    515
    516
            return fulfillArgs;
    517
        }
    518
    519
        /// @dev Helper function to create FulfillArgs for multiple actors
    520
        function _createMultiActorFulfillArgs(
    521
            address[] memory controllers,
    522
            uint256[] memory amounts
    523
        ) internal view returns (ISuperVaultStrategy.FulfillArgs memory) {
    524
            uint256 numActors = controllers.length;
    525
    526 41×
            address[] memory hooks = new address[](numActors);
    527 50×
            bytes[] memory hookCalldata = new bytes[](numActors);
    528 43×
            uint256[] memory expectedAssetsOrSharesOut = new uint256[](numActors);
    529 50×
            bytes32[][] memory globalProofs = new bytes32[][](numActors);
    530 48×
            bytes32[][] memory strategyProofs = new bytes32[][](numActors);
    531
    532 13×
            for (uint256 i = 0; i < numActors; i++) {
    533 26×
                hooks[i] = _getRedeemHookForType(
    534
                    _getYieldSourceTypeFromAddress(_getYieldSource())
    535
                );
    536
    537
                if (
    538 13×
                    _getYieldSourceTypeFromAddress(_getYieldSource()) ==
    539
                    YieldSourceType.ERC4626
    540
                ) {
    541 44×
                    hookCalldata[i] = abi.encodePacked(
    542
                        bytes32(0),
    543
                        _getYieldSource(),
    544
                        address(superVaultStrategy),
    545 17×
                        amounts[i],
    546
                        false
    547
                    );
    548
                } else {
    549 43×
                    hookCalldata[i] = abi.encodePacked(
    550
                        bytes32(0),
    551
                        _getYieldSource(),
    552 15×
                        amounts[i],
    553
                        false
    554
                    );
    555
                }
    556
    557 34×
                expectedAssetsOrSharesOut[i] = amounts[i];
    558 41×
                globalProofs[i] = new bytes32[](0);
    559 41×
                strategyProofs[i] = new bytes32[](0);
    560
            }
    561
    562
            return
    563 39×
                ISuperVaultStrategy.FulfillArgs({
    564
                    controllers: controllers,
    565
                    hooks: hooks,
    566
                    hookCalldata: hookCalldata,
    567
                    expectedAssetsOrSharesOut: expectedAssetsOrSharesOut,
    568
                    globalProofs: globalProofs,
    569
                    strategyProofs: strategyProofs
    570
                });
    571
        }
    572
    573
        /// @dev Helper function to create FulfillArgs for redeem requests
    574
        function _createFulfillRedeemArgs(
    575
            uint256 amount
    576
        ) internal view returns (ISuperVaultStrategy.FulfillArgs memory) {
    577 26×
            address[] memory controllers = new address[](1);
    578 25×
            controllers[0] = _getActor();
    579
    580 30×
            address[] memory hooks = new address[](1);
    581 29×
            hooks[0] = _getRedeemHookForType(
    582
                _getYieldSourceTypeFromAddress(_getYieldSource())
    583
            );
    584
    585 35×
            bytes[] memory hookCalldata = new bytes[](1);
    586
            if (
    587 13×
                _getYieldSourceTypeFromAddress(_getYieldSource()) ==
    588
                YieldSourceType.ERC4626
    589
            ) {
    590 41×
                hookCalldata[0] = abi.encodePacked(
    591
                    bytes32(0),
    592
                    _getYieldSource(),
    593
                    address(superVaultStrategy),
    594
                    amount,
    595
                    false
    596
                );
    597
            } else {
    598 43×
                hookCalldata[0] = abi.encodePacked(
    599
                    bytes32(0),
    600
                    _getYieldSource(),
    601
                    amount,
    602
                    false
    603
                );
    604
            }
    605
    606 29×
            uint256[] memory expectedAssetsOrSharesOut = new uint256[](1);
    607 20×
            expectedAssetsOrSharesOut[0] = amount;
    608
    609 34×
            bytes32[][] memory globalProofs = new bytes32[][](1);
    610 33×
            globalProofs[0] = new bytes32[](0);
    611
    612 34×
            bytes32[][] memory strategyProofs = new bytes32[][](1);
    613 37×
            strategyProofs[0] = new bytes32[](0);
    614
    615
            return
    616 38×
                ISuperVaultStrategy.FulfillArgs({
    617
                    controllers: controllers,
    618
                    hooks: hooks,
    619
                    hookCalldata: hookCalldata,
    620
                    expectedAssetsOrSharesOut: expectedAssetsOrSharesOut,
    621
                    globalProofs: globalProofs,
    622
                    strategyProofs: strategyProofs
    623
                });
    624
        }
    625
    626
        /// @dev Helper function to create ExecuteArgs for depositing assets into yield strategy
    627
        function _createExecuteDepositArgs(
    628
            uint256 amount
    629
        ) internal view returns (ISuperVaultStrategy.ExecuteArgs memory) {
    630 29×
            address[] memory hooks = new address[](1);
    631 29×
            hooks[0] = _getApproveAndDepositHookForType(
    632
                _getYieldSourceTypeFromAddress(_getYieldSource())
    633
            );
    634
    635 37×
            bytes[] memory hookCalldata = new bytes[](1);
    636
            YieldSourceType sourceType = _getYieldSourceTypeFromAddress(
    637
                _getYieldSource()
    638
            );
    639
    640 15×
            if (sourceType == YieldSourceType.ERC4626) {
    641
                // ERC4626 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, bool usePrevAmount
    642 44×
                hookCalldata[0] = abi.encodePacked(
    643
                    bytes32(0), // oracle ID placeholder
    644
                    _getYieldSource(),
    645 67×
                    superVault.asset(), // token address
    646
                    amount,
    647
                    false // don't use previous amount
    648
                );
    649 13×
            } else if (sourceType == YieldSourceType.ERC5115) {
    650
                // ERC5115 deposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address receiver, uint256 id, bool usePrevAmount
    651
                hookCalldata[0] = abi.encodePacked(
    652
                    bytes32(0),
    653
                    _getYieldSource(),
    654 67×
                    superVault.asset(), // token address
    655
                    amount,
    656
                    address(superVaultStrategy),
    657
                    uint256(0), // sy ID for ERC5115
    658
                    false
    659
                );
    660 13×
            } else if (sourceType == YieldSourceType.ERC7540) {
    661
                // ERC7540 requestDeposit: bytes32 oracleId, address yieldSource, address token, uint256 assets, address controller, bool usePrevAmount
    662 36×
                hookCalldata[0] = abi.encodePacked(
    663
                    bytes32(0),
    664
                    _getYieldSource(),
    665 67×
                    superVault.asset(), // token address
    666
                    amount,
    667
                    address(superVaultStrategy),
    668
                    false
    669
                );
    670
            }
    671
    672 29×
            uint256[] memory expectedAssetsOrSharesOut = new uint256[](1);
    673
            // For deposit hooks, we expect shares back. Calculate expected shares based on yield source conversion
    674
            // Use a very small amount (1) as minimum since we just want the deposit to succeed
    675 20×
            expectedAssetsOrSharesOut[0] = 1; // Minimum expected shares from deposit
    676
    677 34×
            bytes32[][] memory globalProofs = new bytes32[][](1);
    678 33×
            globalProofs[0] = new bytes32[](0);
    679
    680 34×
            bytes32[][] memory strategyProofs = new bytes32[][](1);
    681 32×
            strategyProofs[0] = new bytes32[](0);
    682
    683
            return
    684 27×
                ISuperVaultStrategy.ExecuteArgs({
    685
                    hooks: hooks,
    686
                    hookCalldata: hookCalldata,
    687
                    expectedAssetsOrSharesOut: expectedAssetsOrSharesOut,
    688
                    globalProofs: globalProofs,
    689
                    strategyProofs: strategyProofs
    690
                });
    691
        }
    692
    693
        /// @dev Helper function to create FulfillArgs for redeeming from yield strategy
    694
        function _createFulfillRedeemFromStrategyArgs(
    695
            uint256 sharesToRedeem
    696
        ) internal view returns (ISuperVaultStrategy.FulfillArgs memory) {
    697 26×
            address[] memory controllers = new address[](1);
    698 23×
            controllers[0] = _getActor();
    699
    700
            // Get the actual share balance that the SuperVaultStrategy holds in the yield source
    701
            address yieldSource = _getYieldSource();
    702 61×
            uint256 strategyYieldSourceShares = IERC20(yieldSource).balanceOf(
    703
                address(superVaultStrategy)
    704
            );
    705
    706
            // Use the pending shares for the hook calldata to match what's expected by fulfillRedeemRequests
    707 73×
            uint256 pendingShares = superVault.pendingRedeemRequest(0, _getActor());
    708
    709
            // For debugging: ensure we don't try to redeem more than the strategy has
    710 12×
            uint256 actualSharesToRedeem = strategyYieldSourceShares < pendingShares
    711
                ? strategyYieldSourceShares
    712
                : pendingShares;
    713
    714 30×
            address[] memory hooks = new address[](1);
    715 27×
            hooks[0] = _getRedeemHookForType(
    716
                _getYieldSourceTypeFromAddress(yieldSource)
    717
            );
    718
    719 37×
            bytes[] memory hookCalldata = new bytes[](1);
    720
            YieldSourceType sourceType = _getYieldSourceTypeFromAddress(
    721
                yieldSource
    722
            );
    723
    724
            if (
    725 14×
                sourceType == YieldSourceType.ERC4626 ||
    726 0
                sourceType == YieldSourceType.ERC5115
    727
            ) {
    728
                // For ERC4626/5115: bytes32 oracleId, address yieldSource, address owner, uint256 shares, bool usePrevAmount
    729
                // Use the calculated shares to redeem (limited by what strategy actually has)
    730 39×
                hookCalldata[0] = abi.encodePacked(
    731
                    bytes32(0),
    732
                    yieldSource,
    733
                    address(superVaultStrategy), // owner is the strategy
    734
                    actualSharesToRedeem, // redeem calculated amount
    735
                    false
    736
                );
    737
            } else {
    738
                // For ERC7540: bytes32 oracleId, address yieldSource, uint256 shares, bool usePrevAmount
    739 0
                hookCalldata[0] = abi.encodePacked(
    740 0
                    bytes32(0),
    741 0
                    yieldSource,
    742 0
                    actualSharesToRedeem, // redeem calculated amount
    743
                    false
    744
                );
    745
            }
    746
    747
            // For fulfillment, we expect to get assets back from redeeming the shares
    748
            // Use a conservative estimate for expected assets out
    749 29×
            uint256[] memory expectedAssetsOrSharesOut = new uint256[](1);
    750 29×
            expectedAssetsOrSharesOut[0] = actualSharesToRedeem > 0 ? 1 : 0; // Minimum 1 asset expected if redeeming anything
    751
    752 34×
            bytes32[][] memory globalProofs = new bytes32[][](1);
    753 33×
            globalProofs[0] = new bytes32[](0);
    754
    755 34×
            bytes32[][] memory strategyProofs = new bytes32[][](1);
    756 37×
            strategyProofs[0] = new bytes32[](0);
    757
    758
            return
    759 39×
                ISuperVaultStrategy.FulfillArgs({
    760
                    controllers: controllers,
    761
                    hooks: hooks,
    762
                    hookCalldata: hookCalldata,
    763
                    expectedAssetsOrSharesOut: expectedAssetsOrSharesOut,
    764
                    globalProofs: globalProofs,
    765
                    strategyProofs: strategyProofs
    766
                });
    767
        }
    768
    }
    100% test/recon/targets/ManagersTargets.sol
    Lines covered: 14 / 14 (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
    // Target functions that are effectively inherited from the Actor and AssetManagers
    12
    // Once properly standardized, managers will expose these by default
    13
    // Keeping them out makes your project more custom
    14
    abstract contract ManagersTargets is BaseTargetFunctions, Properties {
    15
        // == ACTOR HANDLERS == //
    16
    17
        /// @dev Start acting as another actor
    18
        /// @dev Update ghosts here to make global property checks not fail falsely
    19 20×
        function switchActor(uint256 entropy) public updateGhosts {
    20
            _switchActor(entropy);
    21
        }
    22
    23
        /// @dev Starts using a new asset
    24 20×
        function switch_asset(uint256 entropy) public {
    25
            _switchAsset(entropy);
    26
        }
    27
    28
        /// @dev Deploy a new token and add it to the list of assets, then set it as the current asset
    29 25×
        function add_new_asset(uint8 decimals) public returns (address) {
    30
            address newAsset = _newAsset(decimals);
    31
            return newAsset;
    32
        }
    33
    34
        /// @dev Switches the current vault based on the entropy
    35
        /// @param entropy The entropy to choose a random vault in the array for switching
    36 20×
        function switch_vault(uint256 entropy) public {
    37
            _switchVault(entropy);
    38
        }
    39
    40
        /// @dev Deploy a new vault using the current asset and add it to the list of vaults,
    41
        /// then set it as the current vault
    42 15×
        function add_new_vault() public {
    43 68×
            _newVault(superVault.asset());
    44
        }
    45
    46
        /// === GHOST UPDATING HANDLERS ===///
    47
        /// We `updateGhosts` cause you never know (e.g. donations)
    48
        /// If you don't want to track donations, remove the `updateGhosts`
    49
    50
        /// @dev Approve to arbitrary address, uses Actor by default
    51
        /// NOTE: You're almost always better off setting approvals in `Setup`
    52 20×
        function asset_approve(
    53
            address to,
    54
            uint128 amt
    55
        ) public updateGhosts asActor {
    56 132×
            MockERC20(superVault.asset()).approve(to, amt);
    57
        }
    58
    59
        /// @dev Mint to arbitrary address, uses owner by default, even though MockERC20 doesn't check
    60 20×
        function asset_mint(address to, uint128 amt) public updateGhosts asAdmin {
    61 118×
            MockERC20(superVault.asset()).mint(to, amt);
    62
        }
    63
    }
    92% test/recon/targets/OracleTargets.sol
    Lines covered: 36 / 39 (92%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    // External dependencies
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {Panic} from "@recon/Panic.sol";
    8
    9
    // Source dependencies
    10
    import {IECDSAPPSOracle} from "src/interfaces/oracles/IECDSAPPSOracle.sol";
    11
    import "test/mocks/MockYieldSourceOracle.sol";
    12
    import "../mocks/MockERC4626YieldSourceOracle.sol";
    13
    import "../mocks/MockERC5115YieldSourceOracle.sol";
    14
    import "../mocks/MockERC7540YieldSourceOracle.sol";
    15
    16
    // Test suite dependencies
    17
    import {BeforeAfter} from "../BeforeAfter.sol";
    18
    import {Properties} from "../Properties.sol";
    19
    20
    abstract contract OracleTargets is BaseTargetFunctions, Properties {
    21
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    22 15×
        function yieldSourceOracle_setValidAsset_clamped() public {
    23 69×
            mockERC4626YieldSourceOracle_setValidAsset(superVault.asset(), true);
    24
        }
    25
    26 20×
        function mockERC4626YieldSourceOracle_setValidAsset(
    27
            address asset,
    28
            bool isValid
    29
        ) public asActor {
    30 16×
            MockERC4626YieldSourceOracle(address(erc4626YieldSourceOracle))
    31 0
                .setValidAsset(asset, isValid);
    32
        }
    33
    34 20×
        function mockERC5115YieldSourceOracle_setValidAsset(
    35
            address asset,
    36
            bool isValid
    37
        ) public asActor {
    38 16×
            MockERC5115YieldSourceOracle(address(erc5115YieldSourceOracle))
    39 0
                .setValidAsset(asset, isValid);
    40
        }
    41
    42 20×
        function mockERC7540YieldSourceOracle_setValidAsset(
    43
            address asset,
    44
            bool isValid
    45
        ) public asActor {
    46 16×
            MockERC7540YieldSourceOracle(address(erc7540YieldSourceOracle))
    47 0
                .setValidAsset(asset, isValid);
    48
        }
    49
    50 30×
        function ECDSAPPSOracle_updatePPS_clamped(uint256 pps) public {
    51
            pps %= (100_000_000 * 1e18) + 1; // clamp to a reasonable max price
    52
    53 25×
            address[] memory strategies = new address[](1);
    54 31×
            strategies[0] = address(superVaultStrategy);
    55
    56 34×
            bytes[][] memory proofsArray = new bytes[][](1);
    57 36×
            proofsArray[0] = new bytes[](0);
    58
    59 30×
            uint256[] memory ppss = new uint256[](1);
    60 20×
            ppss[0] = pps;
    61
    62 30×
            uint256[] memory ppsStdevs = new uint256[](1);
    63 20×
            ppsStdevs[0] = 0;
    64
    65 30×
            uint256[] memory validatorSets = new uint256[](1);
    66 20×
            validatorSets[0] = 0;
    67
    68 30×
            uint256[] memory totalValidators = new uint256[](1);
    69 20×
            totalValidators[0] = 0;
    70
    71 30×
            uint256[] memory timestamps = new uint256[](1);
    72 20×
            timestamps[0] = block.timestamp;
    73
    74 38×
            IECDSAPPSOracle.UpdatePPSArgs memory args = IECDSAPPSOracle
    75
                .UpdatePPSArgs({
    76
                    strategies: strategies,
    77
                    proofsArray: proofsArray,
    78
                    ppss: ppss,
    79
                    ppsStdevs: ppsStdevs,
    80
                    validatorSets: validatorSets,
    81
                    totalValidators: totalValidators,
    82
                    timestamps: timestamps
    83
                });
    84
    85
            ECDSAPPSOracle_updatePPS(args);
    86
        }
    87
    88
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    89 21×
        function ECDSAPPSOracle_updatePPS(
    90
            IECDSAPPSOracle.UpdatePPSArgs memory args
    91
        ) public asActor {
    92 54×
            ECDSAPPSOracle.updatePPS(args);
    93
    94
            hasUpdatedPPS = true;
    95
        }
    96
    }
    100% test/recon/targets/SuperGovernorTargets.sol
    Lines covered: 37 / 37 (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/SuperGovernor.sol";
    14
    15
    abstract contract SuperGovernorTargets is BaseTargetFunctions, Properties {
    16
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    17 20×
        function superGovernor_proposeGlobalHooksRoot_clamped(
    18
            bytes32 newRoot
    19
        ) public {
    20 73×
            (bytes32 testRoot, bytes32[][] memory testProofs) = merkleHelper
    21
                .generateTestHooksRoot(
    22
                    address(approveAndDeposit4626Hook),
    23
                    address(redeem4626Hook),
    24
                    _getYieldSource(),
    25 67×
                    superVault.asset()
    26
                );
    27
    28
            superGovernor_proposeGlobalHooksRoot(testRoot);
    29
        }
    30
    31 15×
        function superGovernor_proposeUpkeepPaymentsChange_clamped() public {
    32
            superGovernor_proposeUpkeepPaymentsChange(true);
    33
        }
    34
    35 20×
        function superGovernor_proposeFee_clamped(
    36
            uint256 feeTypeAsUint,
    37
            uint256 value
    38
        ) public {
    39
            feeTypeAsUint %= 3;
    40 12×
            superGovernor_proposeFee(FeeType(feeTypeAsUint), value);
    41
        }
    42
    43 26×
        function superGovernor_executeFeeUpdate_clamped(
    44
            uint256 feeTypeAsUint
    45
        ) public {
    46
            feeTypeAsUint %= 3;
    47 11×
            superGovernor_executeFeeUpdate(FeeType(feeTypeAsUint));
    48
        }
    49
    50
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    51
    52 23×
        function superGovernor_proposeFee(
    53
            FeeType feeType,
    54
            uint256 value
    55
        ) public asAdmin {
    56 60×
            superGovernor.proposeFee(feeType, value);
    57
        }
    58
    59 20×
        function superGovernor_executeFeeUpdate(FeeType feeType) public asAdmin {
    60 20×
            superGovernor.executeFeeUpdate(feeType);
    61
        }
    62
    63 20×
        function superGovernor_proposeMinStaleness(
    64
            uint256 newMinStaleness
    65
        ) public asAdmin {
    66 16×
            superGovernor.proposeMinStaleness(newMinStaleness);
    67
        }
    68
    69 15×
        function superGovernor_executeMinStalenesChange() public asAdmin {
    70 39×
            superGovernor.executeMinStalenesChange();
    71
        }
    72
    73 15×
        function superGovernor_executeRemoveIncentiveTokens() public asAdmin {
    74 39×
            superGovernor.executeRemoveIncentiveTokens();
    75
        }
    76
    77 24×
        function superGovernor_executeUpkeepClaim(uint256 amount) public asAdmin {
    78 58×
            superGovernor.executeUpkeepClaim(amount);
    79
        }
    80
    81 20×
        function superGovernor_proposeUpkeepPaymentsChange(
    82
            bool enabled
    83
        ) public asAdmin {
    84 16×
            superGovernor.proposeUpkeepPaymentsChange(enabled);
    85
        }
    86
    87 15×
        function superGovernor_executeUpkeepPaymentsChange() public asAdmin {
    88 39×
            superGovernor.executeUpkeepPaymentsChange();
    89
        }
    90
    91 20×
        function superGovernor_proposeAddIncentiveTokens(
    92
            address[] memory tokens
    93
        ) public asAdmin {
    94 20×
            superGovernor.proposeAddIncentiveTokens(tokens);
    95
        }
    96
    97 15×
        function superGovernor_executeAddIncentiveTokens() public asAdmin {
    98 39×
            superGovernor.executeAddIncentiveTokens();
    99
        }
    100
    101 20×
        function superGovernor_proposeGlobalHooksRoot(
    102
            bytes32 newRoot
    103
        ) public asAdmin {
    104 16×
            superGovernor.proposeGlobalHooksRoot(newRoot);
    105
        }
    106
    }
    100% test/recon/targets/SuperVaultAggregatorTargets.sol
    Lines covered: 56 / 56 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    import {vm} from "@chimera/Hevm.sol";
    6
    import {Panic} from "@recon/Panic.sol";
    7
    8
    import {ISuperVaultStrategy} from "src/interfaces/SuperVault/ISuperVaultStrategy.sol";
    9
    import "src/SuperVault/SuperVaultAggregator.sol";
    10
    11
    import {BeforeAfter} from "../BeforeAfter.sol";
    12
    import {Properties} from "../Properties.sol";
    13
    14
    abstract contract SuperVaultAggregatorTargets is
    15
        BaseTargetFunctions,
    16
        Properties
    17
    {
    18
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    19 15×
        function superVaultAggregator_proposeChangePrimaryManager_clamped() public {
    20
            superVaultAggregator_proposeChangePrimaryManager(
    21
                address(superVaultStrategy),
    22
                _getActor()
    23
            );
    24
        }
    25
    26 15×
        function superVaultAggregator_executeChangePrimaryManager_clamped() public {
    27
            superVaultAggregator_executeChangePrimaryManager(
    28
                address(superVaultStrategy)
    29
            );
    30
        }
    31
    32 20×
        function superVaultAggregator_createVault_clamped(
    33
            uint256 minUpdateInterval,
    34
            uint256 maxStaleness,
    35
            uint256 performanceFeeBps,
    36
            uint256 managementFeeBps
    37
        ) public {
    38
            // Clamp values to reasonable ranges
    39
            minUpdateInterval = minUpdateInterval % 3601; // Max 1 hour
    40 13×
            maxStaleness = (maxStaleness % 86400) + 301; // Between 5 minutes and 1 day
    41
            performanceFeeBps = performanceFeeBps % 9001; // Max 90%
    42
            managementFeeBps = managementFeeBps % 5001; // Max 50%
    43
    44
            // Create secondary managers array
    45 30×
            address[] memory secondaryManagers = new address[](1);
    46
    47
            ISuperVaultAggregator.VaultCreationParams
    48 73×
                memory params = ISuperVaultAggregator.VaultCreationParams({
    49
                    asset: _getAsset(),
    50
                    name: "SuperVault",
    51
                    symbol: "SV",
    52
                    mainManager: address(this),
    53
                    secondaryManagers: secondaryManagers,
    54
                    minUpdateInterval: minUpdateInterval,
    55
                    maxStaleness: maxStaleness,
    56 20×
                    feeConfig: ISuperVaultStrategy.FeeConfig({
    57
                        performanceFeeBps: performanceFeeBps,
    58
                        managementFeeBps: managementFeeBps,
    59
                        recipient: address(this)
    60
                    })
    61
                });
    62
    63
            superVaultAggregator_createVault(params);
    64
        }
    65
    66
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    67
    68 20×
        function superVaultAggregator_addAuthorizedCaller(
    69
            address strategy,
    70
            address caller
    71
        ) public asAdmin {
    72 22×
            superVaultAggregator.addAuthorizedCaller(strategy, caller);
    73
        }
    74
    75 20×
        function superVaultAggregator_addSecondaryManager(
    76
            address strategy,
    77
            address manager
    78
        ) public asActor {
    79 22×
            superVaultAggregator.addSecondaryManager(strategy, manager);
    80
        }
    81
    82
        /// @dev removed because only callable by oracle
    83
        // function superVaultAggregator_batchForwardPPS(
    84
        //     ISuperVaultAggregator.BatchForwardPPSArgs memory args
    85
        // ) public asActor {
    86
        //     superVaultAggregator.batchForwardPPS(args);
    87
        // }
    88
    89
        /// @dev irrelevant for testing because we're bypassing hook validation
    90
        // function superVaultAggregator_changeGlobalLeavesStatus(
    91
        //     bytes32[] memory leaves,
    92
        //     bool[] memory statuses,
    93
        //     address strategy
    94
        // ) public asActor {
    95
        //     superVaultAggregator.changeGlobalLeavesStatus(
    96
        //         leaves,
    97
        //         statuses,
    98
        //         strategy
    99
        //     );
    100
        // }
    101
    102 20×
        function superVaultAggregator_claimUpkeep(uint256 amount) public asActor {
    103 16×
            superVaultAggregator.claimUpkeep(amount);
    104
        }
    105
    106 21×
        function superVaultAggregator_createVault(
    107
            ISuperVaultAggregator.VaultCreationParams memory params
    108
        ) public asActor {
    109
            (
    110
                address _superVault,
    111
                address _strategy,
    112
                address _escrow
    113 71×
            ) = superVaultAggregator.createVault(params);
    114
    115 13×
            superVault = SuperVault(_superVault);
    116 16×
            superVaultStrategy = SuperVaultStrategy(payable(_strategy));
    117 12×
            superVaultEscrow = SuperVaultEscrow(_escrow);
    118
    119
            hasDeployedNewVault = true;
    120
        }
    121
    122 20×
        function superVaultAggregator_depositStake(
    123
            address manager,
    124
            uint256 amount
    125
        ) public asActor {
    126 22×
            superVaultAggregator.depositStake(manager, amount);
    127
        }
    128
    129 20×
        function superVaultAggregator_depositUpkeep(uint256 amount) public asActor {
    130 26×
            superVaultAggregator.depositUpkeep(_getActor(), amount);
    131
        }
    132
    133 20×
        function superVaultAggregator_executeChangePrimaryManager(
    134
            address strategy
    135
        ) public asActor {
    136 16×
            superVaultAggregator.executeChangePrimaryManager(strategy);
    137
        }
    138
    139
        /// @dev removed because we're bypassing hook validation
    140
        // function superVaultAggregator_executeStrategyHooksRootUpdate(
    141
        //     address strategy
    142
        // ) public asActor {
    143
        //     superVaultAggregator.executeStrategyHooksRootUpdate(strategy);
    144
        // }
    145
    146
        /// @dev removed because only callable by oracle
    147
        // function superVaultAggregator_forwardPPS(
    148
        //     address updateAuthority,
    149
        //     ISuperVaultAggregator.ForwardPPSArgs memory args
    150
        // ) public asActor {
    151
        //     superVaultAggregator.forwardPPS(updateAuthority, args);
    152
        // }
    153
    154 20×
        function superVaultAggregator_proposeChangePrimaryManager(
    155
            address strategy,
    156
            address newManager
    157
        ) public asActor {
    158 22×
            superVaultAggregator.proposeChangePrimaryManager(strategy, newManager);
    159
        }
    160
    161
        /// @dev removed because we're bypassing hook validation
    162
        // function superVaultAggregator_proposeStrategyHooksRoot(
    163
        //     address strategy,
    164
        //     bytes32 newRoot
    165
        // ) public asActor {
    166
        //     superVaultAggregator.proposeStrategyHooksRoot(strategy, newRoot);
    167
        // }
    168
    169 20×
        function superVaultAggregator_removeAuthorizedCaller(
    170
            address strategy,
    171
            address caller
    172
        ) public asActor {
    173 22×
            superVaultAggregator.removeAuthorizedCaller(strategy, caller);
    174
        }
    175
    176 20×
        function superVaultAggregator_removeSecondaryManager(
    177
            address strategy,
    178
            address manager
    179
        ) public asActor {
    180 22×
            superVaultAggregator.removeSecondaryManager(strategy, manager);
    181
        }
    182
    183 20×
        function superVaultAggregator_updatePPSVerificationThresholds(
    184
            address strategy,
    185
            uint256 dispersionThreshold_,
    186
            uint256 deviationThreshold_,
    187
            uint256 mnThreshold_
    188
        ) public asActor {
    189 16×
            superVaultAggregator.updatePPSVerificationThresholds(
    190
                strategy,
    191
                dispersionThreshold_,
    192
                deviationThreshold_,
    193
                mnThreshold_
    194
            );
    195
        }
    196
    197 20×
        function superVaultAggregator_withdrawStake(uint256 amount) public asActor {
    198 16×
            superVaultAggregator.withdrawStake(amount);
    199
        }
    200
    201 20×
        function superVaultAggregator_withdrawUpkeep(
    202
            uint256 amount
    203
        ) public asActor {
    204 16×
            superVaultAggregator.withdrawUpkeep(amount);
    205
        }
    206
    }
    100% test/recon/targets/SuperVaultEscrowTargets.sol
    Lines covered: 6 / 6 (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/SuperVault/SuperVaultEscrow.sol";
    14
    15
    abstract contract SuperVaultEscrowTargets is
    16
        BaseTargetFunctions,
    17
        Properties
    18
    {
    19
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    20
    21
    22
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    23
    24 20×
        function superVaultEscrow_escrowShares(address from, uint256 amount) public asActor {
    25 22×
            superVaultEscrow.escrowShares(from, amount);
    26
        }
    27
    28 20×
        function superVaultEscrow_initialize(address vaultAddress, address strategyAddress) public asActor {
    29 22×
            superVaultEscrow.initialize(vaultAddress, strategyAddress);
    30
        }
    31
    32 20×
        function superVaultEscrow_returnShares(address to, uint256 amount) public asActor {
    33 22×
            superVaultEscrow.returnShares(to, amount);
    34
        }
    35
    }
    100% test/recon/targets/SuperVaultStrategyTargets.sol
    Lines covered: 36 / 36 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    5
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {Panic} from "@recon/Panic.sol";
    8
    import {MockERC20} from "@recon/MockERC20.sol";
    9
    10
    import "src/SuperVault/SuperVaultStrategy.sol";
    11
    12
    import {YieldSourceType} from "test/recon/managers/YieldManager.sol";
    13
    import {BeforeAfter, OpType} from "../BeforeAfter.sol";
    14
    import {Properties} from "../Properties.sol";
    15
    16
    abstract contract SuperVaultStrategyTargets is BaseTargetFunctions, Properties {
    17
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    18
    19
        /// @dev Clamps the action type to 0 to add a vault as a yield source
    20 20×
        function superVaultStrategy_manageYieldSource_clamped(
    21
            uint256 sourceType
    22
        ) public {
    23 16×
            YieldSourceType clampedType = YieldSourceType(sourceType % 3); // 0=ERC4626, 1=ERC5115, 2=ERC7540
    24
            address yieldSourceOracle = _getYieldSourceOracleForType(clampedType);
    25
    26
            superVaultStrategy_manageYieldSource(
    27
                _getYieldSource(),
    28
                yieldSourceOracle,
    29
                0
    30
            );
    31
        }
    32
    33 20×
        function superVaultStrategy_handleOperations7540_clamped(
    34
            uint256 operation,
    35
            uint256 amount
    36
        ) public {
    37
            operation %= 7; // clamp by the possible operation types in the enum
    38
            superVaultStrategy_handleOperations7540(
    39 11×
                ISuperVaultStrategy.Operation(operation),
    40
                _getActor(),
    41
                _getActor(),
    42
                amount
    43
            );
    44
        }
    45
    46
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    47
    48 17×
        function superVaultStrategy_executeVaultFeeConfigUpdate() public asActor {
    49 65×
            superVaultStrategy.executeVaultFeeConfigUpdate();
    50
        }
    51
    52 20×
        function superVaultStrategy_handleOperations4626Deposit(
    53
            address controller,
    54
            uint256 assetsGross
    55
        ) public asActor {
    56 46×
            superVaultStrategy.handleOperations4626Deposit(controller, assetsGross);
    57
        }
    58
    59 29×
        function superVaultStrategy_handleOperations4626Mint(
    60
            address controller,
    61
            uint256 sharesNet,
    62
            uint256 assetsGross,
    63
            uint256 assetsNet
    64
        ) public asActor {
    65 53×
            superVaultStrategy.handleOperations4626Mint(
    66
                controller,
    67
                sharesNet,
    68
                assetsGross,
    69
                assetsNet
    70
            );
    71
        }
    72
    73 20×
        function superVaultStrategy_handleOperations7540(
    74
            ISuperVaultStrategy.Operation operation,
    75
            address controller,
    76
            address receiver,
    77
            uint256 amount
    78
        ) public asActor {
    79 18×
            superVaultStrategy.handleOperations7540(
    80
                operation,
    81
                controller,
    82
                receiver,
    83
                amount
    84
            );
    85
        }
    86
    87 20×
        function superVaultStrategy_manageEmergencyWithdraw(
    88
            uint8 action,
    89
            address recipient,
    90
            uint256 amount
    91
        ) public asActor {
    92 16×
            superVaultStrategy.manageEmergencyWithdraw(action, recipient, amount);
    93
        }
    94
    95 28×
        function superVaultStrategy_manageYieldSource(
    96
            address source,
    97
            address oracle,
    98
            uint8 actionType
    99
        ) public asActor {
    100 91×
            superVaultStrategy.manageYieldSource(source, oracle, actionType);
    101
        }
    102
    103 20×
        function superVaultStrategy_manageYieldSources(
    104
            address[] memory sources,
    105
            address[] memory oracles,
    106
            uint8[] memory actionTypes
    107
        ) public asActor {
    108 24×
            superVaultStrategy.manageYieldSources(sources, oracles, actionTypes);
    109
        }
    110
    111 20×
        function superVaultStrategy_moveAccumulatorOnTransfer(
    112
            address from,
    113
            address to,
    114
            uint256 shares
    115
        ) public asActor {
    116 24×
            superVaultStrategy.moveAccumulatorOnTransfer(from, to, shares);
    117
        }
    118
    119 20×
        function superVaultStrategy_proposeVaultFeeConfigUpdate(
    120
            uint256 performanceFeeBps,
    121
            uint256 managementFeeBps,
    122
            address recipient
    123
        ) public asActor {
    124 16×
            superVaultStrategy.proposeVaultFeeConfigUpdate(
    125
                performanceFeeBps,
    126
                managementFeeBps,
    127
                recipient
    128
            );
    129
        }
    130
    131 20×
        function superVaultStrategy_updateMaxPPSSlippage(
    132
            uint256 maxSlippageBps
    133
        ) public asActor {
    134 16×
            superVaultStrategy.updateMaxPPSSlippage(maxSlippageBps);
    135
        }
    136
    }
    100% test/recon/targets/SuperVaultTargets.sol
    Lines covered: 123 / 123 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    // Recon deps
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {Panic} from "@recon/Panic.sol";
    8
    import {MockERC20} from "@recon/MockERC20.sol";
    9
    10
    import "src/SuperVault/SuperVault.sol";
    11
    12
    import {BeforeAfter, OpType} from "test/recon/BeforeAfter.sol";
    13
    import {Properties} from "../Properties.sol";
    14
    15
    /// @dev All receivers are inherently clamped to actors to make checking properties easier
    16
    abstract contract SuperVaultTargets is BaseTargetFunctions, Properties {
    17
        /// CUSTOM TARGET FUNCTIONS - Add your own target functions here ///
    18 20×
        function superVault_requestRedeem_clamped(uint256 shares) public {
    19 76×
            shares %= superVault.balanceOf(_getActor()) + 1;
    20
    21
            superVault_requestRedeem(shares);
    22
        }
    23
    24 20×
        function superVault_redeem_clamped(uint256 shares) public {
    25 66×
            uint256 claimableAssets = superVault.maxWithdraw(_getActor());
    26 63×
            uint256 claimableShares = superVault.convertToShares(claimableAssets);
    27
    28 13×
            shares %= claimableShares + 1;
    29
    30
            superVault_redeem(shares);
    31
        }
    32
    33 20×
        function superVault_withdraw_clamped(uint256 assets) public {
    34 66×
            uint256 claimableAssets = superVault.maxWithdraw(_getActor());
    35 13×
            assets %= claimableAssets + 1;
    36
    37
            superVault_withdraw(assets);
    38
        }
    39
    40
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    41
    42 20×
        function superVault_approve(address spender, uint256 value) public asActor {
    43 74×
            superVault.approve(spender, value);
    44
        }
    45
    46 20×
        function superVault_burnShares(uint256 amount) public asActor {
    47 16×
            superVault.burnShares(amount);
    48
        }
    49
    50
        /// @dev Property: pendingRedeemRequest should be 0 after a user calls cancelRedeem
    51
        /// @dev Property: averageRequestPPS should be 0 after a user calls cancelRedeem
    52
        /// @dev Property: user shouldn't receive more than convertToAssets(pendingRedeemRequest) after cancelRedeem
    53 15×
        function superVault_cancelRedeem_ASSERTION_CANCEL_REDEEM_NO_OVERPAY()
    54
            public
    55
            updateGhostsWithOpType(OpType.CANCEL)
    56
        {
    57 71×
            uint256 pendingRedeemRequestsBefore = superVault.pendingRedeemRequest(
    58
                0,
    59
                _getActor()
    60
            );
    61 63×
            uint256 pendingRedeemRequestsAsAssets = superVault.convertToAssets(
    62
                pendingRedeemRequestsBefore
    63
            );
    64 127×
            uint256 balanceBefore = MockERC20(superVault.asset()).balanceOf(
    65
                _getActor()
    66
            );
    67
    68 41×
            vm.prank(_getActor());
    69 53×
            superVault.cancelRedeem(_getActor());
    70
    71 72×
            uint256 pendingRedeemRequestsAfter = superVault.pendingRedeemRequest(
    72
                0,
    73
                _getActor()
    74
            );
    75 67×
            uint256 averageRequestPPS = superVaultStrategy
    76
                .getSuperVaultState(_getActor())
    77
                .averageRequestPPS;
    78 128×
            uint256 balanceAfter = MockERC20(superVault.asset()).balanceOf(
    79
                _getActor()
    80
            );
    81
    82
            // Checks
    83
            eq(
    84
                pendingRedeemRequestsAfter,
    85
                0,
    86 17×
                ASSERTION_CANCEL_REDEEM_NO_OVERPAY
    87
            );
    88
            eq(
    89
                averageRequestPPS,
    90
                0,
    91 17×
                ASSERTION_CANCEL_REDEEM_NO_OVERPAY
    92
            );
    93
            lte(
    94
                balanceAfter - balanceBefore,
    95
                pendingRedeemRequestsAsAssets,
    96 17×
                ASSERTION_CANCEL_REDEEM_NO_OVERPAY
    97
            );
    98
        }
    99
    100
        /// @dev Property: previewDeposit returns the correct amounts compared to executing a deposit
    101 20×
        function superVault_deposit_ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION(
    102
            uint256 assets
    103
        ) public updateGhostsWithOpType(OpType.ADD) {
    104 67×
            uint256 previewShares = superVault.previewDeposit(assets);
    105
    106 41×
            vm.prank(_getActor());
    107 82×
            uint256 shares = superVault.deposit(assets, _getActor());
    108
    109
            eq(
    110
                previewShares,
    111
                shares,
    112 17×
                ASSERTION_PREVIEW_DEPOSIT_MATCHES_EXECUTION
    113
            );
    114
        }
    115
    116
        /// @dev Property: previewMint returns the correct amounts compared to executing a mint
    117 20×
        function superVault_mint_ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION(
    118
            uint256 shares
    119
        ) public updateGhostsWithOpType(OpType.ADD) {
    120 67×
            uint256 previewMint = superVault.previewMint(shares);
    121
    122 41×
            vm.prank(_getActor());
    123 82×
            uint256 assets = superVault.mint(shares, _getActor());
    124
    125
            eq(
    126
                assets,
    127
                previewMint,
    128 17×
                ASSERTION_PREVIEW_MINT_MATCHES_EXECUTION
    129
            );
    130
        }
    131
    132 20×
        function superVault_invalidateNonce(bytes32 nonce) public asActor {
    133 16×
            superVault.invalidateNonce(nonce);
    134
        }
    135
    136 20×
        function superVault_redeem(
    137
            uint256 shares
    138
        ) public updateGhostsWithOpType(OpType.REMOVE) asActor {
    139
            superVault.redeem(shares, _getActor(), _getActor());
    140
        }
    141
    142 20×
        function superVault_withdraw(
    143
            uint256 assets
    144
        ) public updateGhostsWithOpType(OpType.REMOVE) asActor {
    145
            superVault.withdraw(assets, _getActor(), _getActor());
    146
        }
    147
    148 26×
        function superVault_requestRedeem(
    149
            uint256 shares
    150
        ) public updateGhostsWithOpType(OpType.REQUEST) asActor {
    151 81×
            superVault.requestRedeem(shares, _getActor(), _getActor());
    152
        }
    153
    154 20×
        function superVault_setOperator(
    155
            uint256 entropy,
    156
            bool approved
    157
        ) public asActor {
    158
            address operator = _getRandomActor(entropy);
    159 65×
            superVault.setOperator(operator, approved);
    160
        }
    161
    162
        /// @dev Propery: _update should never revert
    163
        /// @dev Property: Transfers of shares should transfer the exact amount of accumulatorShares to the recipient
    164
        /// @dev Property: Transfers of shares should transfer the exact amount of accumulatorCostBasis to the recipient
    165
        // NOTE: _update only gets called on transfer of Vault shares
    166 20×
        function superVault_transfer_ASSERTION_TRANSFER_SHARES_CONSERVED(
    167
            uint256 entropy,
    168
            uint256 value
    169
        ) public updateGhostsWithOpType(OpType.TRANSFER) {
    170
            address to = _getRandomActor(entropy);
    171
            ISuperVaultStrategy.SuperVaultState
    172 60×
                memory stateSenderBefore = superVaultStrategy.getSuperVaultState(
    173
                    _getActor()
    174
                );
    175
            ISuperVaultStrategy.SuperVaultState
    176 59×
                memory stateRecipientBefore = superVaultStrategy.getSuperVaultState(
    177
                    to
    178
                );
    179
    180 41×
            vm.prank(_getActor());
    181 107×
            try superVault.transfer(to, value) {
    182
                ISuperVaultStrategy.SuperVaultState
    183 60×
                    memory stateSenderAfter = superVaultStrategy.getSuperVaultState(
    184
                        _getActor()
    185
                    );
    186
                ISuperVaultStrategy.SuperVaultState
    187 59×
                    memory stateRecipientAfter = superVaultStrategy
    188
                        .getSuperVaultState(to);
    189
    190
                eq(
    191 10×
                    stateSenderBefore.accumulatorShares -
    192
                        stateSenderAfter.accumulatorShares,
    193 10×
                    stateRecipientAfter.accumulatorShares -
    194
                        stateRecipientBefore.accumulatorShares,
    195 17×
                    ASSERTION_TRANSFER_SHARES_CONSERVED
    196
                );
    197
                eq(
    198 10×
                    stateSenderBefore.accumulatorCostBasis -
    199
                        stateSenderAfter.accumulatorCostBasis,
    200
                    stateRecipientAfter.accumulatorCostBasis -
    201
                        stateRecipientBefore.accumulatorCostBasis,
    202
                    ASSERTION_TRANSFER_SHARES_CONSERVED
    203
                );
    204
            } catch (bytes memory err) {
    205
                bool expectedError;
    206 23×
                expectedError = checkError(
    207
                    err,
    208
                    "ERC20InsufficientBalance(address,uint256,uint256)"
    209
                );
    210 22×
                t(expectedError, ASSERTION_TRANSFER_SHARES_CONSERVED);
    211
            }
    212
        }
    213
    214
        /// @dev Propery: _update should never revert
    215
        // NOTE: _update only gets called on transfer of Vault shares
    216 20×
        function superVault_transferFrom_ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM(
    217
            uint256 entropyFrom,
    218
            uint256 entropyTo,
    219
            uint256 value
    220
        ) public updateGhostsWithOpType(OpType.TRANSFER) {
    221
            address from = _getRandomActor(entropyFrom);
    222
            address to = _getRandomActor(entropyTo);
    223
    224 41×
            vm.prank(_getActor());
    225 109×
            try superVault.transferFrom(from, to, value) {} catch (
    226
                bytes memory err
    227
            ) {
    228
                bool expectedError;
    229
                expectedError =
    230 25×
                    checkError(
    231
                        err,
    232
                        "ERC20InsufficientBalance(address,uint256,uint256)"
    233
                    ) ||
    234 21×
                    checkError(
    235
                        err,
    236
                        "ERC20InsufficientAllowance(address,uint256,uint256)"
    237
                    );
    238
                t(
    239
                    expectedError,
    240 17×
                    ASSERTION_UPDATE_SHOULD_NOT_REVERT_TRANSFER_FROM
    241
                );
    242
            }
    243
        }
    244
    245
        /// @dev removed because signature components not fuzzable
    246
        // function superVault_authorizeOperator(
    247
        //     address controller,
    248
        //     address operator,
    249
        //     bool approved,
    250
        //     bytes32 nonce,
    251
        //     uint256 deadline,
    252
        //     bytes memory signature
    253
        // ) public asActor {
    254
        //     superVault.authorizeOperator(
    255
        //         controller,
    256
        //         operator,
    257
        //         approved,
    258
        //         nonce,
    259
        //         deadline,
    260
        //         signature
    261
        //     );
    262
        // }
    263
    }
    94% test/recon/targets/YieldSourceTargets.sol
    Lines covered: 166 / 176 (94%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    // Chimera deps
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    8
    // Helpers
    9
    import {Panic} from "@recon/Panic.sol";
    10
    11
    import {Properties} from "../Properties.sol";
    12
    import {MockERC4626Tester, FunctionType, RevertType} from "../mocks/MockERC4626Tester.sol";
    13
    import {MockERC5115Tester, RevertType as RevertType5115} from "../mocks/MockERC5115Tester.sol";
    14
    import {MockERC7540Tester} from "../mocks/MockERC7540Tester.sol";
    15
    import {YieldSourceType} from "../managers/YieldManager.sol";
    16
    17
    /// @dev Target functions for yield source testers which are used as yield sources in SuperVaultStrategy
    18
    abstract contract YieldSourceTargets is BaseTargetFunctions, Properties {
    19
        /// AUTO GENERATED TARGET FUNCTIONS - WARNING: DO NOT DELETE OR MODIFY THIS LINE ///
    20
    21
        /// ERC20 functions (available across all types as they inherit from MockERC20) ///
    22 20×
        function yieldSource_approve(
    23
            address spender,
    24
            uint256 value
    25
        ) public asActor {
    26
            YieldSourceType currentType = _getCurrentYieldSourceType();
    27
            address yieldSource = _getYieldSource();
    28
    29 13×
            if (currentType == YieldSourceType.ERC4626) {
    30 19×
                MockERC4626Tester(yieldSource).approve(spender, value);
    31 13×
            } else if (currentType == YieldSourceType.ERC5115) {
    32 0
                MockERC5115Tester(yieldSource).approve(spender, value);
    33 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    34 19×
                MockERC7540Tester(yieldSource).approve(spender, value);
    35
            }
    36
        }
    37
    38 27×
        function yieldSource_setDecimalsOffset(
    39
            uint8 targetDecimalsOffset
    40
        ) public asActor {
    41
            YieldSourceType currentType = _getCurrentYieldSourceType();
    42
            address yieldSource = _getYieldSource();
    43
    44 14×
            if (currentType == YieldSourceType.ERC4626) {
    45 13×
                MockERC4626Tester(yieldSource).setDecimalsOffset(
    46
                    targetDecimalsOffset
    47
                );
    48
            }
    49
            // Note: ERC5115 and ERC7540 don't have setDecimalsOffset function
    50
        }
    51
    52 20×
        function yieldSource_transfer(address to, uint256 value) public asActor {
    53
            YieldSourceType currentType = _getCurrentYieldSourceType();
    54
            address yieldSource = _getYieldSource();
    55
    56 15×
            if (currentType == YieldSourceType.ERC4626) {
    57 73×
                MockERC4626Tester(yieldSource).transfer(to, value);
    58 13×
            } else if (currentType == YieldSourceType.ERC5115) {
    59 0
                MockERC5115Tester(yieldSource).transfer(to, value);
    60 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    61 71×
                MockERC7540Tester(yieldSource).transfer(to, value);
    62
            }
    63
        }
    64
    65 20×
        function yieldSource_transferFrom(
    66
            address from,
    67
            address to,
    68
            uint256 value
    69
        ) public asActor {
    70
            YieldSourceType currentType = _getCurrentYieldSourceType();
    71
            address yieldSource = _getYieldSource();
    72
    73 13×
            if (currentType == YieldSourceType.ERC4626) {
    74 73×
                MockERC4626Tester(yieldSource).transferFrom(from, to, value);
    75 13×
            } else if (currentType == YieldSourceType.ERC5115) {
    76 0
                MockERC5115Tester(yieldSource).transferFrom(from, to, value);
    77 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    78 73×
                MockERC7540Tester(yieldSource).transferFrom(from, to, value);
    79
            }
    80
        }
    81
    82
        /// Core Vault Functions (ERC4626/ERC4626-like) ///
    83 20×
        function yieldSource_deposit(
    84
            uint256 assets,
    85
            address receiver
    86
        ) public asActor {
    87
            YieldSourceType currentType = _getCurrentYieldSourceType();
    88
            address yieldSource = _getYieldSource();
    89
    90 12×
            if (currentType == YieldSourceType.ERC4626) {
    91 19×
                MockERC4626Tester(yieldSource).deposit(assets, receiver);
    92
            }
    93
            // Note: ERC5115 has different deposit signature, handled separately
    94
            // Note: ERC7540 has different deposit signature, handled separately
    95
        }
    96
    97 20×
        function yieldSource_mint(uint256 shares, address receiver) public asActor {
    98
            YieldSourceType currentType = _getCurrentYieldSourceType();
    99
            address yieldSource = _getYieldSource();
    100
    101 13×
            if (currentType == YieldSourceType.ERC4626) {
    102 71×
                MockERC4626Tester(yieldSource).mint(shares, receiver);
    103 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    104 71×
                MockERC7540Tester(yieldSource).mint(shares, receiver);
    105
            }
    106
            // Note: ERC5115 doesn't have mint function
    107
        }
    108
    109 20×
        function yieldSource_withdraw(
    110
            uint256 assets,
    111
            address receiver,
    112
            address owner
    113
        ) public asActor {
    114
            YieldSourceType currentType = _getCurrentYieldSourceType();
    115
            address yieldSource = _getYieldSource();
    116
    117 15×
            if (currentType == YieldSourceType.ERC4626) {
    118 75×
                MockERC4626Tester(yieldSource).withdraw(assets, receiver, owner);
    119 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    120 21×
                MockERC7540Tester(yieldSource).withdraw(assets, receiver, owner);
    121
            }
    122
            // Note: ERC5115 doesn't have withdraw function
    123
        }
    124
    125 20×
        function yieldSource_redeem(
    126
            uint256 shares,
    127
            address receiver,
    128
            address owner
    129
        ) public asActor {
    130
            YieldSourceType currentType = _getCurrentYieldSourceType();
    131
            address yieldSource = _getYieldSource();
    132
    133 13×
            if (currentType == YieldSourceType.ERC4626) {
    134 21×
                MockERC4626Tester(yieldSource).redeem(shares, receiver, owner);
    135 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    136 21×
                MockERC7540Tester(yieldSource).redeem(shares, receiver, owner);
    137
            }
    138
            // Note: ERC5115 has different redeem signature, handled separately
    139
        }
    140
    141
        /// ERC5115-specific functions ///
    142 20×
        function yieldSource_deposit5115(
    143
            address receiver,
    144
            address tokenIn,
    145
            uint256 amountTokenToDeposit,
    146
            uint256 minSharesOut,
    147
            bool depositFromInternalBalance
    148
        ) public asActor {
    149
            YieldSourceType currentType = _getCurrentYieldSourceType();
    150
            address yieldSource = _getYieldSource();
    151
    152 12×
            if (currentType == YieldSourceType.ERC5115) {
    153 0
                MockERC5115Tester(yieldSource).deposit(
    154
                    receiver,
    155
                    tokenIn,
    156
                    amountTokenToDeposit,
    157
                    minSharesOut,
    158
                    depositFromInternalBalance
    159
                );
    160
            }
    161
        }
    162
    163 20×
        function yieldSource_redeem5115(
    164
            address receiver,
    165
            uint256 amountSharesToRedeem,
    166
            address tokenOut,
    167
            uint256 minTokenOut,
    168
            bool burnFromInternalBalance
    169
        ) public asActor {
    170
            YieldSourceType currentType = _getCurrentYieldSourceType();
    171
            address yieldSource = _getYieldSource();
    172
    173 12×
            if (currentType == YieldSourceType.ERC5115) {
    174 0
                MockERC5115Tester(yieldSource).redeem(
    175
                    receiver,
    176
                    amountSharesToRedeem,
    177
                    tokenOut,
    178
                    minTokenOut,
    179
                    burnFromInternalBalance
    180
                );
    181
            }
    182
        }
    183
    184
        /// ERC7540-specific functions ///
    185 20×
        function yieldSource_setOperator(
    186
            address operator,
    187
            bool approved
    188
        ) public asActor {
    189
            YieldSourceType currentType = _getCurrentYieldSourceType();
    190
            address yieldSource = _getYieldSource();
    191
    192 12×
            if (currentType == YieldSourceType.ERC7540) {
    193 13×
                MockERC7540Tester(yieldSource).setOperator(operator, approved);
    194
            }
    195
        }
    196
    197 20×
        function yieldSource_requestDeposit(
    198
            uint256 assets,
    199
            address controller,
    200
            address owner
    201
        ) public asActor {
    202
            YieldSourceType currentType = _getCurrentYieldSourceType();
    203
            address yieldSource = _getYieldSource();
    204
    205 12×
            if (currentType == YieldSourceType.ERC7540) {
    206 15×
                MockERC7540Tester(yieldSource).requestDeposit(
    207
                    assets,
    208
                    controller,
    209
                    owner
    210
                );
    211
            }
    212
        }
    213
    214 20×
        function yieldSource_requestRedeem(
    215
            uint256 shares,
    216
            address controller,
    217
            address owner
    218
        ) public asActor {
    219
            YieldSourceType currentType = _getCurrentYieldSourceType();
    220
            address yieldSource = _getYieldSource();
    221
    222 12×
            if (currentType == YieldSourceType.ERC7540) {
    223 15×
                MockERC7540Tester(yieldSource).requestRedeem(
    224
                    shares,
    225
                    controller,
    226
                    owner
    227
                );
    228
            }
    229
        }
    230
    231 20×
        function yieldSource_cancelDepositRequest(
    232
            uint256 requestId,
    233
            address controller
    234
        ) public asActor {
    235
            YieldSourceType currentType = _getCurrentYieldSourceType();
    236
            address yieldSource = _getYieldSource();
    237
    238 12×
            if (currentType == YieldSourceType.ERC7540) {
    239 15×
                MockERC7540Tester(yieldSource).cancelDepositRequest(
    240
                    requestId,
    241
                    controller
    242
                );
    243
            }
    244
        }
    245
    246 20×
        function yieldSource_cancelRedeemRequest(
    247
            uint256 requestId,
    248
            address controller
    249
        ) public asActor {
    250
            YieldSourceType currentType = _getCurrentYieldSourceType();
    251
            address yieldSource = _getYieldSource();
    252
    253 12×
            if (currentType == YieldSourceType.ERC7540) {
    254 15×
                MockERC7540Tester(yieldSource).cancelRedeemRequest(
    255
                    requestId,
    256
                    controller
    257
                );
    258
            }
    259
        }
    260
    261 20×
        function yieldSource_claimCancelDepositRequest(
    262
            uint256 requestId,
    263
            address receiver,
    264
            address controller
    265
        ) public asActor {
    266
            YieldSourceType currentType = _getCurrentYieldSourceType();
    267
            address yieldSource = _getYieldSource();
    268
    269 12×
            if (currentType == YieldSourceType.ERC7540) {
    270 15×
                MockERC7540Tester(yieldSource).claimCancelDepositRequest(
    271
                    requestId,
    272
                    receiver,
    273
                    controller
    274
                );
    275
            }
    276
        }
    277
    278 20×
        function yieldSource_claimCancelRedeemRequest(
    279
            uint256 requestId,
    280
            address receiver,
    281
            address controller
    282
        ) public asActor {
    283
            YieldSourceType currentType = _getCurrentYieldSourceType();
    284
            address yieldSource = _getYieldSource();
    285
    286 12×
            if (currentType == YieldSourceType.ERC7540) {
    287 67×
                MockERC7540Tester(yieldSource).claimCancelRedeemRequest(
    288
                    requestId,
    289
                    receiver,
    290
                    controller
    291
                );
    292
            }
    293
        }
    294
    295 20×
        function yieldSource_deposit7540(
    296
            uint256 assets,
    297
            address receiver,
    298
            address controller
    299
        ) public asActor {
    300
            YieldSourceType currentType = _getCurrentYieldSourceType();
    301
            address yieldSource = _getYieldSource();
    302
    303 12×
            if (currentType == YieldSourceType.ERC7540) {
    304 15×
                MockERC7540Tester(yieldSource).deposit(
    305
                    assets,
    306
                    receiver,
    307
                    controller
    308
                );
    309
            }
    310
        }
    311
    312
        /// Testing-specific functions (ERC4626 only) ///
    313 20×
        function yieldSource_setRevertBehavior4626(
    314
            uint8 functionType,
    315
            uint8 revertType
    316
        ) public asActor {
    317
            YieldSourceType currentType = _getCurrentYieldSourceType();
    318
            address yieldSource = _getYieldSource();
    319
    320 12×
            if (currentType == YieldSourceType.ERC4626) {
    321 20×
                MockERC4626Tester(yieldSource).setRevertBehavior(
    322 13×
                    FunctionType(functionType),
    323 13×
                    RevertType(revertType)
    324
                );
    325
            }
    326
        }
    327
    328
        /// Testing-specific functions (ERC5115 only) ///
    329 20×
        function yieldSource_setRevertBehavior5115(
    330
            uint8 revertType
    331
        ) public asActor {
    332
            YieldSourceType currentType = _getCurrentYieldSourceType();
    333
            address yieldSource = _getYieldSource();
    334
    335 12×
            if (currentType == YieldSourceType.ERC5115) {
    336 0
                MockERC5115Tester(yieldSource).setRevertBehavior(
    337 0
                    RevertType5115(revertType)
    338
                );
    339
            }
    340
        }
    341
    342
        /// Common yield manipulation functions ///
    343
    344 20×
        function yieldSource_simulateLoss(uint256 lossAmount) public {
    345
            YieldSourceType currentType = _getCurrentYieldSourceType();
    346
            address yieldSource = _getYieldSource();
    347
    348 13×
            if (currentType == YieldSourceType.ERC4626) {
    349 13×
                MockERC4626Tester(yieldSource).simulateLoss(lossAmount);
    350 13×
            } else if (currentType == YieldSourceType.ERC5115) {
    351 0
                MockERC5115Tester(yieldSource).simulateLoss(lossAmount);
    352 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    353 13×
                MockERC7540Tester(yieldSource).simulateLoss(lossAmount);
    354
            }
    355
        }
    356
    357 20×
        function yieldSource_simulateGain(uint256 gainAmount) public {
    358
            YieldSourceType currentType = _getCurrentYieldSourceType();
    359
            address yieldSource = _getYieldSource();
    360
    361 13×
            if (currentType == YieldSourceType.ERC4626) {
    362 13×
                MockERC4626Tester(yieldSource).simulateGain(gainAmount);
    363 13×
            } else if (currentType == YieldSourceType.ERC5115) {
    364 0
                MockERC5115Tester(yieldSource).simulateGain(gainAmount);
    365 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    366 13×
                MockERC7540Tester(yieldSource).simulateGain(gainAmount);
    367
            }
    368
        }
    369
    370 20×
        function yieldSource_setLossOnWithdraw(
    371
            uint256 lossOnWithdraw
    372
        ) public asActor {
    373
            YieldSourceType currentType = _getCurrentYieldSourceType();
    374
            address yieldSource = _getYieldSource();
    375
    376 13×
            if (currentType == YieldSourceType.ERC4626) {
    377 13×
                MockERC4626Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw);
    378 13×
            } else if (currentType == YieldSourceType.ERC5115) {
    379 0
                MockERC5115Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw);
    380 12×
            } else if (currentType == YieldSourceType.ERC7540) {
    381 13×
                MockERC7540Tester(yieldSource).setLossOnWithdraw(lossOnWithdraw);
    382
            }
    383
        }
    384
    385
        /// Yield source management functions ///
    386 17×
        function yieldSource_switchToERC4626() public {
    387
            _switchYieldSource(0); // Switch to first yield source (ERC4626)
    388
        }
    389
    390 15×
        function yieldSource_switchToERC5115() public {
    391
            _switchYieldSource(1); // Switch to second yield source (ERC5115)
    392
        }
    393
    394 15×
        function yieldSource_switchToERC7540() public {
    395
            _switchYieldSource(2); // Switch to third yield source (ERC7540)
    396
        }
    397
    398 20×
        function yieldSource_switchRandom(uint256 entropy) public {
    399
            // Randomly switch between the three deployed yield sources
    400
            _switchYieldSource(entropy);
    401
        }
    402
    }